ASP.NET WEB API not binding to dynamic object on POST - c#

If have the following Api Controller ... using StrutureMap for the DI ...
using System;
using System.Dynamic;
using System.Net;
using System.Net.Http;
using System.Web.Http;
using IdentityService.Domain;
using IdentityService.Domain.Contracts;
using IdentityService.Domain.Models;
namespace IdentityService.Controllers
{
public class AccountController : ApiController
{
private readonly IRepository<Client> _clientRepository;
private readonly IRepository<RelyingParty> _relyingPartyRepository;
private readonly IRepository<Token> _tokenRepository;
public AccountController(
IRepository<Client> clientRepository,
IRepository<RelyingParty> relyingPartyRepository,
IRepository<Token> tokenRepository)
{
_clientRepository = clientRepository;
_relyingPartyRepository = relyingPartyRepository;
_tokenRepository = tokenRepository;
}
public HttpResponseMessage Post(
[FromBody] dynamic data)
{
dynamic result = new ExpandoObject();
try
{
var clientAuthenticator = new ClientAuthenticator(
_clientRepository,
_relyingPartyRepository,
_tokenRepository);
Token token;
clientAuthenticator.Authenticate(
data.Key,
data.ChecksumValue,
data.Checksum,
data.Name,
data.Password,
out token);
result.Token = token;
}
catch (Exception ex)
{
result.ErrorCode = ex.GetType().ToString();
result.ErrorMessage = ex.GetBaseException().Message;
}
return this.Request.CreateResponse(HttpStatusCode.OK, (ExpandoObject)result);
}
}
}
Using Fiddler, I am make the following post:
POST http://localhost:54029/api/account HTTP/1.1
User-Agent: Fiddler
Host: localhost:54029
Content-Type: "application/json"
Content-Length: 218
{
"Key": "7d42276d3c3954716c672543385b575836472f5d543d7776205627413a",
"ChecksumValue": "127.0.0.1",
"Checksum": "ao4Ei77BaX1/iMZMTAJxWzt4fxc=",
"Name": "USER_NAME",
"Password": "PASSWORD"
}
Any idea why my data would be null? I have tried switching to JObject, with no success. All the examples I have found makes me think this should work.
Here is the complete response:
HTTP/1.1 200 OK
Cache-Control: no-cache
Pragma: no-cache
Content-Type: application/json; charset=utf-8
Expires: -1
Server: Microsoft-IIS/8.0
X-AspNet-Version: 4.0.30319
X-SourceFiles: =?UTF-8?B?QzpcY29kZS1tYXR0cnVtYVx0YWxrLWF1dGhlbnRpY2F0aW9uLXNlcnZlclxJZGVudGl0eVNlcnZpY2VcYXBpXGFjY291bnQ=?=
X-Powered-By: ASP.NET
Date: Mon, 27 May 2013 13:59:45 GMT
Content-Length: 137
{"ErrorCode":"Microsoft.CSharp.RuntimeBinder.RuntimeBinderException","ErrorMessage":"Cannot perform runtime binding on a null reference"}
Any help would be much appreciated!
Update
I tried just a simple example, like:
public async Task<dynamic> Post(dynamic data)
{
var body = await Request.Content.ReadAsStringAsync();
return data;
}
The parameter data is still null, but I can see the values in body.

Remove the quotes from "application/json".
Content-Type: application/json

In an MVC 6 controller (which extends from Controller and not ApiController) the following does work (with report being JSON) :
[HttpPost("[action]")]
public void RunReport([FromBody]dynamic report)
{
....
}
Updated: For MVC 5 this is what I use
[HttpPost]
public async Task<HttpResponseMessage> FBLogin(Newtonsoft.Json.Linq.JObject jObject)
{
dynamic data = (dynamic)jObject;
string accessToken = data.accessToken;
...
}
Where the JSON payload is :
'{accessToken: "EAAJF9eVIKOsBABdKVNOLJyfyCnnkrl8mlW2crgZC1NYsDqgq9ZBIZC......" }'

if you make the param from [FromBody] to dynamic, and if its a JSON object (made with JSON.stringify) then you can just use .ToString() to get the string value and you should be OK
public void Post(string token, [FromBody]dynamic value)
{
int userID = db.GetUserIdByToken(token);
db.InsertJson(userID, value.ToString());
}
other definitions is headers: {'Content-Type': 'application/json'},

remove [FromBody] attribute and it should work

Related

How to get form data from Postman To WebApi

I want to receive form data from Postman:
Content-Type: application/json
Here is WebApi method:
[HttpPost]
[Route("api/test")]
public async Task TestMethod(HttpRequestMessage request)
{
var test = await request.Content.ReadAsStringAsync();
}
What I'm getting is:
------WebKitFormBoundarypqDvmeG89cBR9mK9
Content-Disposition: form-data; name="test"
esad
------WebKitFormBoundarypqDvmeG89cBR9mK9--
But I don't want data with WebKitFormBoundary and I've restriction to use formdata only. Is there any other way?
HTTP call information:
POST /api/test HTTP/1.1
Host: localhost:16854
Content-Type: multipart/form-data; boundary=----WebKitFormBoundary7MA4YWxkTrZu0gW
Cache-Control: no-cache
Postman-Token: 1a3d6427-4956-707d-da0c-3a29a63c7563
------WebKitFormBoundary7MA4YWxkTrZu0gW
Content-Disposition: form-data; name="test"
esad
------WebKitFormBoundary7MA4YWxkTrZu0gW--
Curl call information:
curl -X POST \
http://localhost:16854/api/test \
-H 'cache-control: no-cache' \
-H 'content-type: multipart/form-data; boundary=----WebKitFormBoundary7MA4YWxkTrZu0gW' \
-H 'postman-token: 02055873-e9a8-e9a6-019c-b407992b0e2f' \
-F test=esad
1) If you have to send Content-Type: multipart/form-data OR simply form-data
This is the first tab of Postman
If you have to collect only one key/value pair of your posted form-data
[HttpPost]
[Route("api/test")]
public HttpResponseMessage TestMethod(HttpRequestMessage request)
{
var testValue = HttpContext.Current.Request.Form["test"];
return Request.CreateResponse(HttpStatusCode.OK, testValue);
}
If you have to collect more than one key/value pair of your posted form-data
[HttpPost]
[Route("api/test")]
public HttpResponseMessage TestMethod(HttpRequestMessage request)
{
NameValueCollection collection = HttpContext.Current.Request.Form;
var items = collection.AllKeys.SelectMany(collection.GetValues, (k, v) => new { key = k, value = v });
//We just collect your multiple form data key/value pair in this dictinary
//The following code will be replaced by yours
Dictionary<string, string> keyValuePairs = new Dictionary<string, string>();
foreach (var item in items)
{
keyValuePairs.Add(item.key, item.value);
}
return Request.CreateResponse(HttpStatusCode.OK, keyValuePairs);
}
2) If you have to send Content-Type: application/x-www-form-urlencoded
This is the second tab of Postman
Then your API will be
[HttpPost]
[Route("api/test")]
public async Task TestMethod(HttpRequestMessage request)
{
var test = await request.Content.ReadAsFormDataAsync();
}
Then you will get following output when you debug your code with breakpoint
3) If you have to send Content-Type: application/json
This is the third tab of Postman
See below screenshot for such option
And your api is
[HttpPost]
[Route("api/test")]
public async Task TestMethod(HttpRequestMessage request)
{
var jObject = await request.Content.ReadAsAsync<JObject>();
Item item = JsonConvert.DeserializeObject<Item>(jObject.ToString());
}
And your model to collect this posted data
public class Item
{
public string test { get; set; }
}
And your output will be
The advantage of this option you can send complex type as posted data and like
And your api is
[HttpPost]
[Route("test")]
public async Task TestMethod(HttpRequestMessage request)
{
var jObject = await request.Content.ReadAsAsync<JObject>();
Sample sample = JsonConvert.DeserializeObject<Sample>(jObject.ToString());
}
And you model to collect this data are
public class Item
{
public string test { get; set; }
}
public class Sample
{
public Item item { get; set; }
}
And you will see the output is
The following code will read key/value correctly when sent from Postman with form-data option selected
[HttpPost]
[Route("api/test")]
public async Task<HttpResponseMessage> TestMethod(HttpRequestMessage request)
{
string root = HttpContext.Current.Server.MapPath("~/App_Data");
var provider = new MultipartFormDataStreamProvider(root);
await Request.Content.ReadAsMultipartAsync(provider);
var testValue = provider.FormData.GetValues("test")[0];
return Request.CreateResponse(HttpStatusCode.OK);
}
A more thorough example can be found here (Section: Reading Form Control Data).
Edit: The HTTP call that is sent to the above API handler is the one below:
POST /api/stats/testmethod HTTP/1.1
Host: localhost:4100
Cache-Control: no-cache
Postman-Token: 999fd13d-f804-4a63-b4df-989b660bcbc5
Content-Type: multipart/form-data; boundary=----WebKitFormBoundary7MA4YWxkTrZu0gW
------WebKitFormBoundary7MA4YWxkTrZu0gW
Content-Disposition: form-data; name="test"
esad
------WebKitFormBoundary7MA4YWxkTrZu0gW--
Below statement can do this:
NameValueCollection form = HttpContext.Current.Request.Form;

ServiceStack: Custom login method and create manual IAuthSession for use with [Authenticate] attribute?

I'm trying to manually create an IAuthSession and saving it, so I can use the attribute [Authenticate] on my methods, but doesn't seem to work.
So, I have my LoginHandler : Service where I do some custom code to login a user, and then I do:
namespace RequestHandlers
{
public class LoginHandler : Service
{
public object Post(Login request)
{
// do magic login code
if (loginSuccess)
{
IAuthSession session = GetSession();
session.FirstName = "My First name"
session.IsAuthenticated = true;
base.Request.SaveSession(session); // save the session??
}
else
{
throw new UnauthorizedAccessException(pc.GetFaultString());
}
return new LoginResponse() { Result = "OK" };
}
}
}
I was then my hope that the base.Request.SaveSession(session); would save the Session so that ServiceStack would later detect it and see that "aha, a protected method is allowed, since the user is logged in".
The response for the Login call is (in Fiddler):
HTTP/1.1 200 OK
Transfer-Encoding: chunked
Content-Type: application/json; charset=utf-8
Vary: Accept
Server: Microsoft-HTTPAPI/2.0
Set-Cookie: ss-id=TwOJExNFhBuVuDna1aDO;path=/;HttpOnly
Set-Cookie: ss-pid=O4bJqgiLWRTFTOgcf2DD;path=/;expires=Mon, 08 Feb 2038 12:39:30 GMT;HttpOnly
X-Powered-By: ServiceStack/5,02 NET45 Win32NT/.NET
Date: Thu, 08 Feb 2018 12:39:31 GMT
f
{"Result":"OK"}
0
So, I get some cookie with a pid, I take that as the session id?
Now, I have the Test method that I after running the Login above, should be available, right? =)
namespace tWorks.Alfa.Modules.ModuleRestApiService.Services.AlfaConnectService.Requests
{
[Authenticate]
[Route("/test")]
public class Test : IReturn<TestResponse>
{
public string Message { get; set; }
}
public class TestResponse
{
public string Result { get; set; }
}
}
But its not, I get a 401 error:
HTTP/1.1 401 Unauthorized
Transfer-Encoding: chunked
Vary: Accept
Server: Microsoft-HTTPAPI/2.0
X-Powered-By: ServiceStack/5,02 NET45 Win32NT/.NET
WWW-Authenticate: basic realm="/auth/basic"
Date: Thu, 08 Feb 2018 12:40:12 GMT
0
The call from Fiddler for Test is this:
POST http://192.168.0.147:8080/alfaconnect/test HTTP/1.1
Host: 192.168.0.147:8080
Accept: application/json
Content-Type: application/json
Content-Length: 18
DeviceUUID: 123asd123
Domain: AlfaOnline
Cookie: ss-id=TwOJExNFhBuVuDna1aDO
Cookie: ss-pid=O4bJqgiLWRTFTOgcf2DD
{"Message": "Hej"}
As you can see, I copied the ss-id and ss-pid from the Login response to the Test call.
What am I missing?
Here is my AppHost:
public class AppHost : AppSelfHostBase
{
public AppHost(IModuleController moduleController, IContactModule contactModule) : base("HttpListener Self-Host", typeof(Services.AlfaProService.AlfaProService).Assembly)
{
}
public override void Configure(Funq.Container container)
{
Plugins.Add(new AuthFeature(() => new AuthUserSession(),
new IAuthProvider[] {
new BasicAuthProvider(), //Sign-in with HTTP Basic Auth
new CredentialsAuthProvider(), //HTML Form post of UserName/Password credentials
}));
container.Register<ICacheClient>(new MemoryCacheClient());
var userRep = new InMemoryAuthRepository();
container.Register<IUserAuthRepository>(userRep);
}
public override RouteAttribute[] GetRouteAttributes(Type requestType)
{
var routes = base.GetRouteAttributes(requestType);
if (requestType.FullName.Contains("AlfaConnectService"))
{
routes.Each(x => x.Path = "/alfaconnect" + x.Path);
}
else if (requestType.FullName.Contains("AlfaProService"))
{
routes.Each(x => x.Path = "/alfapro" + x.Path);
}
return routes;
}
}
}
ServiceStack also requires the session.UserAuthName to be set to the Username.
All ServiceStack's constructs are designed to work together, if you're not going to use ServiceStack's AuthProvider model just ignore it and implement your own Authentication. i.e. ignore all of ServiceStack's built-in Auth/Session features and use your own filters/validation instead.

Recaptcha and Web API - Good REST practice to validate the token

I've got a series of Web API controllers which return data to be consumed by an AngularJS front end.
I'm concerned that it would be super-easy to script a bot that just hammers it with calls and extracts all the data out of my database. Whilst the information is freely available, it took me many hours/weeks/months/years to collate it all - it's really the whole USP of my site. If someone stole my data just by extracting it from the services, I'd be upset.
Simply adding Authorize won't help, because it's free to register.
I've decided that I'll submit some sort of token with each request to the API which will get validated. Primarily this will be a solved Captcha, but it could vary in the future so I'm making it extensible. Therefore I'm sending a token type and the token value in a "?token=TypeID,TokenValue" parameter with a type converter to pick this up and turn it into the Token object.
So my question is - What is good practice to send a token along with a request to a Web API controller? I've done it as follows, is this right? I've read that it's not very RESTful to send a querystring to do the authentication - but it works really well.
Sample proof-of-concept
My controller:
public class ProductsController : ApiController
{
public IHttpActionResult Get(WebApiToken token, int id)
{
if (token == null)
return BadRequest("Token not supplied");
ITokenValidator requestValidator;
// Some kind of a factory to decide how to validate the token that's been provided.
requestValidator = new GuidTokenValidator();
if (requestValidator.ValidateToken(token))
{
return Ok(string.Format("Some JSON would be returned here representing product ID {0}", id));
}
else
return BadRequest("Token validation failed");
}
}
Javascript to invoke it:
<script type="text/javascript">
$(document).ready(function () {
$("#postTest").click(function () {
$.ajax({
type: "GET",
dataType: "json",
url: "/api/Products/2?token=2,OK",
success: function (data) {
alert(data);
},
error: function (error) {
// Error handler
}
});
});
});
</script>
The token definition:
[TypeConverter(typeof(TokenConverter))]
public class WebApiToken : IToken
{
public TokenType TokenType { get; set; }
public string Token { get; set; }
public static bool TryParse(string s, out WebApiToken result)
{
result = null;
var parts = s.Split(',');
if (parts.Length != 2)
{
return false;
}
int tokenTypeId;
if (int.TryParse(parts[0], out tokenTypeId))
{
result = new WebApiToken() { TokenType = (TokenType)tokenTypeId, Token = parts[1] };
return true;
}
return false;
}
}
Type converter:
public class TokenConverter : TypeConverter
{
public override bool CanConvertFrom(ITypeDescriptorContext context, System.Type sourceType)
{
if (sourceType == typeof(string))
{
return true;
}
return base.CanConvertFrom(context, sourceType);
}
public override object ConvertFrom(ITypeDescriptorContext context, System.Globalization.CultureInfo culture, object value)
{
if (value is string)
{
WebApiToken token;
if (WebApiToken.TryParse((string)value, out token))
{
return token;
}
}
return base.ConvertFrom(context, culture, value);
}
}
Fiddler request:
GET http://localhost:58106/api/Products/2?token=2,OK HTTP/1.1 Host: localhost:58106 Connection: keep-alive Accept: application/json, text/javascript, */*; q=0.01 X-Requested-With: XMLHttpRequest User-Agent: Mozilla/5.0 (Windows NT 6.1; WOW64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/42.0.2311.152 Safari/537.36 Referer: http://localhost:58106/ Accept-Encoding: gzip, deflate, sdch Accept-Language: en-GB,en-US;q=0.8,en;q=0.6
Fiddler response:
HTTP/1.1 200 OK
Cache-Control: no-cache
Pragma: no-cache
Content-Type: application/json; charset=utf-8
Expires: -1
Server: Microsoft-IIS/8.0
X-AspNet-Version: 4.0.30319
X-SourceFiles: =?UTF-8?B?YzpcdXNlcnNcYmVuLnNsb2FuXGRvY3VtZW50c1x2aXN1YWwgc3R1ZGlvIDIwMTNcUHJvamVjdHNcV2ViQVBJQWN0aW9uRmlsdGVyc1xXZWJBUElBY3Rpb25GaWx0ZXJzXGFwaVxQcm9kdWN0c1wy?=
X-Powered-By: ASP.NET
Date: Wed, 20 May 2015 11:59:55 GMT
Content-Length: 60
"Some JSON would be returned here representing product ID 2"

aspnet webapi 2 response payload not displayed for HttpStatusCode.Unauthorized

I have an ActionFilterAttribute that overrides the OnActionExecuting. If the user isn't authenticated I want to return and 401 Unauthorized status and a JSON object of type Response with a custom message and other properties
public class Response
{
public Boolean Error { get; set; }
public IList<string> Messages { get; set; }
public object Data { get; set; }
}
That's what I did:
public override void OnActionExecuting(HttpActionContext actionContext)
{
//some code here
var response = new Response();
response.AddMessage(true, Util.Localization.Authentication.Validation_UserNotAuthenticated);
actionContext.Response = new HttpResponseMessage(HttpStatusCode.Unauthorized)
{
Content = new ObjectContent(typeof(Response), response, new JsonMediaTypeFormatter())
};
}
When the client makes a request, that's the Response Header (from google chrome developer tools - network):
HTTP/1.1 401 Unauthorized
Cache-Control: no-cache
Pragma: no-cache
Content-Type: application/json; charset=utf-8
Expires: -1
Server: Microsoft-IIS/8.5
X-AspNet-Version: 4.0.30319
X-Powered-By: ASP.NET
Date: Thu, 18 Dec 2014 13:19:12 GMT
Content-Length: 83
Well, the JSON with the Response object isn't displayed to the client.
If I only change theHttpStatusCode to OK, the JSON is displayed:
actionContext.Response = new HttpResponseMessage(HttpStatusCode.OK)
{
Content = new ObjectContent(typeof(Response), response, new JsonMediaTypeFormatter())
};
Also, if I keep theHttpStatusCode as Unauthorized, but change the Type to string, the text is displayed normally to the client:
actionContext.Response = new HttpResponseMessage(HttpStatusCode.Unauthorized)
{
Content = new ObjectContent(typeof(string), "test string", new JsonMediaTypeFormatter())
};
How can I send a custom JSON object when I get a Unauthorized Http status?
Thnaks
I assume you are using the built-in AuthorizeAttribute on your controller to secure your api. I think the reason it's not working is because the AuthorizationFilters (like AuthorizeAttribute) happen earlier in the WebApi pipeline than ActionFilters. See here for details:
https://damienbod.wordpress.com/2014/01/04/web-api-2-using-actionfilterattribute-overrideactionfiltersattribute-and-ioc-injection/
So your code never executes because the AuthorizeAttribute already failed and returned its default response (401 message with no body).
The easiest way to do what you want is to inherit a custom authorization attribute, ex MyAuthorizeAttribute, inheriting from AuthorizeAttribute and changing the way it handles errors. Then just decorate your controllers with [MyAuthorize] instead of [Authorize].
public class MyAuthorizeAttribute : AuthorizeAttribute
{
protected override void HandleUnauthorizedRequest(HttpActionContext actionContext)
{
var jsonFormatter = new JsonMediaTypeFormatter();
var response = new Response();
response.AddMessage(true, Util.Localization.Authentication.Validation_UserNotAuthenticated);
actionContext.Response = new HttpResponseMessage(HttpStatusCode.Unauthorized)
{
ReasonPhrase = "Unauthorized",
Content = new ObjectContent(typeof(Response), response, new JsonMediaTypeFormatter())
};
}
}

HttpResponseMessage content is not getting serialized for BadRequest responses

I have ASP.NET application, with some MVC and WebAPI controllers in there. MVC 5.1 and WebAPI 2.1. There is no tricky configuration at all, the only thing is getting configured is removed XML formatter:
public static class WebApi
{
public static void Configure(HttpConfiguration config)
{
var formatters = config.Formatters;
formatters.Remove(formatters.XmlFormatter);
}
}
There is simple DTO to put data returned from controller action together:
public class TokenResponse
{
public string token { get; set; }
public string token_type { get; set; }
public int expires_in { get; set; }
public string error { get; set; }
}
And the following code:
[NoHttpResponseCaching]
public class TokenController : ApiController
{
public async Task<HttpResponseMessage> PostAsync(HttpRequestMessage request, TokenRequest tokenRequest)
{
try
{
// do some stuff
var response = new TokenResponse { ... };
return request.CreateResponse(HttpStatusCode.OK, response);
}
catch (TokenRequestValidationException ex)
{
_logger.WriteError(ex);
var response = new TokenResponse { error = ex.ErrorToDisplay };
return request.CreateResponse(HttpStatusCode.BadRequest, response);
}
}
}
The problem is that if everything fine, I have my TokenResponse serialized to JSON, as expected, but if there is exception occurring, execution flow is coming into catch block and response body is equal to "Bad Request", this is the RAW dump of response from Fiddler:
HTTP/1.1 400 Bad Request
Cache-Control: no-store
Pragma: no-cache
Content-Type: text/html
Server: Microsoft-IIS/8.5
X-Powered-By: ASP.NET
Date: Wed, 18 Jun 2014 08:25:53 GMT
Content-Length: 11
Bad Request
Tried to return anonymous object having some random named properties instead of using TokenResponse, getting same response:
return request.CreateResponse(HttpStatusCode.BadRequest, new {klhjaoiubf = "kjhaflkjh"});
And I'm stuck at this point trying to understand why I'm not getting my object serialized in response body and how to change it. Anyone, any ideas, why my object is getting ignored when response code is BadRequest?

Categories