TL,DR; When implementing custom methods, "the HTTP configuration [...] must use the body:* clause and all remaining request message fields shall map to the HTTP request body.". Why?
I have a problem with Google's API Design Guide which I'm attempting to follow with gRPC with Cloud Endpoints.
The HttpRule is used to transcode HTTP/JSON to gRPC. The HttpRule reference states:
Note that when using * in the body mapping, it is not possible to have
HTTP parameters, as all fields not bound by the path end in the body.
[...] The common usage of * is in custom methods which don't use the
URL at all for transferring data.
...an opinion also repeated in Google's Custom Methods documentation and reinforced with Google's API Linter,
When using a named representation in the body mapping there is a well defined space left to add metadata in the form of querystring parameters; E.g. for pagination, links, deprecation warnings, error messages).
service Messaging {
rpc UpdateMessage(UpdateMessageRequest) returns (Message) {
option (google.api.http) = {
put: "/v1/messages/{message_id}"
// A named reference makes it possible to use querystring params
// and the HTTP body.
body: "data"
};
}
}
message UpdateMessageRequest {
message Data {
string foo = 1;
string bar = 2;
string baz = 3;
}
// mapped to the URL as querystring params
bool format = 1;
string revision = 2;
// mapped to the body
Data data = 3;
}
This allows for an HTTP PUT request to /v1/messages/123456?format=true&revision=2 with a body
foo="I am foo"
bar="I am bar"
baz="I am baz"
Since the mapping binds body to the type UpdateMessageRequest.Data, the remaining fields end up in the querystring. This is the approach used in standard methods, but not with custom) methods.
Custom methods must map body to *. The same API with a custom method would be
service Messaging {
rpc UpdateMessage(UpdateMessageRequest) returns (Message) {
option (google.api.http) = {
put: "/v1/messages/{message_id}"
// Every field not bound by the path template should be
// mapped to the request body.
body: "*"
};
}
}
message UpdateMessageRequest {
message Data {
string foo = 1;
string bar = 2;
string baz = 3;
}
// mapped to the body
bool format = 1;
string revision = 2;
Data data = 3;
}
If the same metadata is used across both standard and custom) methods, it must be added either as querystring params, or placed in the body.
For example, an Angular app would use HttpParams
// standard method
const params = new HttpParams().append('format', true).append('revision', 2);
const request = {
foo: "I am foo",
bar: "I am bar",
baz: "I am baz",
}
this.http.post<Document>(url, request, {params});
However, a custom method requires the client to place everything in the body:
// custom method
const request = {
format: true,
revision: 2,
data: {
foo: "I am foo",
bar: "I am bar",
baz: "I am baz",
},
}
this.http.post<Document>(url, request);
Question: What is the reason for this?
Great question.
For reference, I wrote the AIP on this topic as well as the lint rule, and am also the current maintainer of the design guide that you referenced.
First off, I will mention that our latest guidance (linked above) specifically says should rather than must for this. In other words, it is the right thing to do the majority of the time, but there may be exceptions. Nothing in the gRPC transcoding implementation stops you from using a different body -- we tell you to use * for custom methods, but we do not place any technical barriers against doing something else.
I can think of a couple of good "exception cases" where a body other than * might make sense. The first would be a custom method that is modeled on one of the standard methods, but should be custom for some reason. The second would be if a custom method accepted a full resource, and wanted to set the body to that resource. This would make that method consistent with Create and Update, which obviously has value to the API's users.
If you have a case where you have a clear argument for using something else as the body (particularly if that something is the resource itself), by all means, use a different body and tell the linter to be quiet. We wrote "should" for a reason.
You also asked: Why do we have that recommendation in the first place?
There are a few reasons. The biggest one is that the exceptional cases described above are rare. I do hundreds of API reviews internally, and I can not actually think of one off the top of my head (which is not to say they do not exist). The majority of the time, the best thing for users is for the request message to mirror the HTTP payload.
Another reason is a key limitation: assigning a particular field to be the body limits what you can add outside of that field, since query strings are limited in what they can represent in both type (just primitives) and quantity (URI length constraints). Because altering the body later constitutes a breaking change, this ties your hands somewhat. That might be fine for your use case, obviously, but it is important to note.
Anyway, I hope that helps -- oh, and thanks for using my stuff. :-)
Related
I am asked to implement a REST Web API to a specific route, where either of two different Json Datatypes may be posted.
This results in the following exception being thrown:
Microsoft.AspNetCore.Routing.Matching.AmbiguousMatchException: The request matched multiple endpoints.
Is there an Attribute that can be placed on the Web Methods, referencing Properties of the Json payloads so as to disambiguate the two possible Datatypes?
This was covered here but I'll add a little bit.
It's not good API design to do that and goes against Swagger / OpenAPI specifications to do what you're asking.
The only way to do this with the same HTTP method (POST in your case) is to have one action that takes in both models. Check which one isn't null to then route to the correct method to handle that logic.
If you can get away with using a different HTTP verb you could technically do that and have two separate action methods (like POST and PUT), but you wouldn't be using them "correctly" and based on your question and need, I doubt you can do that anyway.
You can read the request body as a string and then try to decide which type to deserialize in:
[HttpPost]
[Route("api/mypath")]
public async Task<IActionResult> MyMethod()
{
request.Body.Position = 0;
var reader = new StreamReader(request.Body, Encoding.UTF8);
var body = await reader.ReadToEndAsync();
if(body.Contains("A))
{
var A = JsonConvert.DeserializeObject<A>(body);
}
else{
var B = JsonConvert.DeserializeObject<B>(body);
}
}
And add a middleware to enable request buffering:
app.Use(next => context => {
context.Request.EnableBuffering();
return next(context);
});
You can read more about it here
I want use ActiveMQ in .net core,i use Apache.NMS.ActiveMQ for doing this but I have problem.
I see this error in ActiveMQ admin console:
Cannot display ObjectMessage body. Reason: Failed to build body from bytes. Reason: java.io.StreamCorruptedException: invalid stream header: 00010000
thats part of my code for doing this:
private const String QUEUE_DESTINATION = "test-queue";
private IConnection _connection;
private ISession _session;
public MessageQueue()
{
IConnectionFactory factory = new ConnectionFactory("tcp://localhost:61616?wireFormat.maxInactivityDuration=5000000");
_connection = factory.CreateConnection();
_connection.Start();
_session = _connection.CreateSession();
}
IDestination dest = _session.GetQueue(QUEUE_DESTINATION);
using (IMessageProducer producer = _session.CreateProducer(dest))
{
var objectMessage = producer.CreateObjectMessage(newDoc);
producer.Send(objectMessage);
}
The fact that the admin console can't display the body of an ObjectMessage isn't really an error. It's the expected behavior. Remember, from the broker's perspective the message body is just an array of bytes. It could be text data (encoded many different ways), image data, custom binary data, etc. The broker has no idea how to decode it. It will try to display the body as text, but if it fails it won't try anything else.
To be clear, in order to see the contents of an ObjectMessage the web console would have to have the object's definition in order to deserialize it. There is no mechanism to tell the web console about arbitrary data formats so it can deserialize message bodies reliably (other than simple text). This is one reason, among many, to avoid ObjectMessage.
I recommend you use a simple text format (e.g. JSON, XML) to represent your data and send that in your message rather than using ObjectMessage.
How do you properly create a Web API POST of complex object or multiple parameters using Angular2?
I have a service component in Angular2 as seen below:
public signin(inputEmail: string, inputPassword: string): Observable<Response> {
return this.http.post('/api/account/signin', JSON.stringify({ Email: inputEmail, Password: inputPassword}), this.options);
}
The targeted web api is seen below:
[HttpPost]
[Route("signin")]
public async Task<IActionResult> Signin(string email, string password)
{
....
}
This does not work because I need to convert the parameters of the web api into a single POCO class entity with Email and Password properties and put the [FromBody] attribute: Signin([FromBody] Credential credential)
Without using [FromURI] (POST requests with query strings?), how can I make POSTs of multiple parameters or complex objects without converting these parameters into a single POCO class?
Because what if I have numerous Web API POST actions with parameters like (string sensitiveInfo1, string name, int sensitiveInfo2) or (ClassifiedInfo info, string sensitiveInfo1, string sensitiveInfo2), do I need to convert them all to POCO classes and always use [FromBody]?
PS.
I was using RestangularJS before and it can posts anything (mulitple primitive objects and complex objects) without my Web API actions having [FromBody] attributes. Will about to investigate how RestangularJS do it.
Without using [FromURI] (POST requests with query strings?), how can I make POSTs of multiple parameters or complex objects without converting these parameters into a single POCO class?
I know its not what you want to hear but out of the box this is not possible. It is not a limitation of the browser code that is making the request. This means it does not matter if you are using Angular, JQuery, straight JavaScript, or even RestangularJS. This is a limitation (I use that word loosely as I am sure this is by design) of Web API (any version). Here is the documentation on this design: Parameter Binding in ASP.NET Web API by Mike Wasson.
At most one parameter is allowed to read from the message body. So this will not work:
// Caution: Will not work!
public HttpResponseMessage Post([FromBody] int id, [FromBody] string name) { ... }
So the question becomes, what are your options?
Create a model
This is the thing you were trying to avoid but I list it first because this is how Web API was intended to behave. I have not yet heard a compelling reason not to do this. This approach allows you to extend your model easily without having to change the method signature. It also allows for model validation on the model itself. Personally I really like this approach.
public class SignInModel{
public string Email {get;set;}
public string Password {get;set;}
}
[HttpPost]
[Route("signin")]
public async Task<IActionResult> Signin(SignInModel signInModel)
{
// ....
}
I did not repeat your existing JavaScript code because what you have works as is with the above web api code
URL
Again, what you were trying to avoid. This does make what you want possible with the limitation that you have to pass these parameters using the Query string on the URL. The JavaScript would change but the signature you had on the Web API method would not.
public signin(inputEmail: string, inputPassword: string): Observable<Response> {
return this.http.post('/api/account/signin/?email=inputEmail&password=inputPassword', null, this.options);
}
I did not repeat your existing Web API code because what you have works as is with the above web JavaScript code (by default FromUri is assumed I believe)
Custom Model Binder
See Passing multiple POST parameters to Web API Controller Methods by Rick Strahl. This option allows you to create a custom model binder that could do what you are asking. It is a whole bunch of extra code though for, IMHO, not much benefit. Maybe there are situations where it would be useful although I really cannot think of any off the top of my head.
Dynamic
Finally you could also pass in a dynamic object as the parameter of your Web API. This is essentially the same as receiving the JSON as a string and making your Controller code responsible for the deserialization of content. Again, I believe that this would make your code worse in most situations as you have to implement custom validation and type checks. This answer was proposed previously on SO by Bes Ley. Again, maybe there are situations where it would be useful although I really cannot think of any off the top of my head.
If you call Web API 2.2 post method from Angular 2 type script, dont forget to add following header content and parameter object.
let headers = new Headers({ 'Content-Type': 'application/x-www-form-urlencoded' });
var params = new URLSearchParams();
params.set('userid', '102');
params.set('username', 'foo');
return this._http.post('http://localhost:6579/api/PostUser', params.toString(), { headers: headers }).map(res => res.json());
Perhaps you should post with options:
{
headers: new Headers({
'Content-Type': 'application/x-www-form-urlencoded'
})
}
and encode data like
jQuery.param({user:'bla', password: 'bla'});
WebAPI does not provide this out of the box. If you try to get understanding of web API bindings, you might be able to figure out why.
I think this article might help.
The generic rules are:
– simple, string-convertible parameters (value types, strings, Guids, DateTimes and so on) are by default read from URI
– complex types are by default read from the body
– collections of simple parameters are by default read from the body too
– you cannot compose a single model based on input from both URI and request body, it has to be one or the other
I have fixed the issue of Angular2 HTTP Post ASP.NET MVC Web API
let headers = new Headers();
headers.append('Content-Type', 'application/x-www-form-urlencoded; charset=utf-8');
let params: URLSearchParams = new URLSearchParams();
params.set('value', '2');
let options = new RequestOptions({
headers: headers//,
//search: params
});
let content = new URLSearchParams();
content.set('StudentName', 'Inderjit Singh';
content.set('Mobile', '+919041165398');
content.set('Nationality', 'Indian');
content.set('AdmissionNo', '6');
content.set('SectionCode', '1');
content.set('Gender', 'Male');
content.set('RegNo', '18585');
content.set('ClassCode', '1');
this.http.post('YOUR_URL', content.toString(), { headers: headers }).map((res: Response) => { console.log("data is==>" + res.text()); }).subscribe();
WebApi will be able to deserialize your Credential object provided the JSON object has the same field names (I am not sure about case so you may be right here). You seem to be missing the headers from the post call in your Angular2 component.
Can you check the Content-Type using Chrome Debugger or Fiddler? It should be application/json.
Try this, passing a complex class object into a single data parameter.
var SearchQuery = function () {
this.Alphabet = null;
this.Search = false;
this.Keyword = null;
this.RegionList = null;
};
var para = new SearchQuery();
{ data: JSON.stringify(para) } - Post Data
you can receive it using a JObject in your API controller and deserialize it as according to your classes.
In learning about the area handling story in MVC6, I keep coming across blog posts that have area:exists in a route pattern, but no matter how hard I search, I can't find anything about this at all in any Microsoft documentation, and none of the blog posts I've found explain what it's doing or mention where that bit of information came from.
Where is this constrained explained, and where is the comprehensive, up-to-date, canonical documentation for built-in route patterns and constraints? For the record, I am aware of this page, but it's structured more like a tutorial than a canonical reference.
If anyone from Microsoft is reading this, http://www.asp.net/mvc/overview/api-reference leads to a page with next to no information and an unsynced table of contents that I can't find what I want within. And your RouteAttribute class reference has no links to anything explaining what a url pattern should look like.
EDIT
So after some deeper digging, I found this:
https://github.com/aspnet/Mvc/blob/48bfdceea6d243c5ec8d6e00f450f8fe7cce59f7/src/Microsoft.AspNet.Mvc.Core/MvcCoreRouteOptionsSetup.cs#L26
So it's related to KnownRouteValueConstraint, which led me to this:
https://github.com/aspnet/Mvc/blob/e0b8532735997c439e11fff68dd342d5af59f05f/src/Microsoft.AspNet.Mvc.Core/KnownRouteValueConstraint.cs#L26-L40
So I guess that means the constraint simply makes sure the captured value is non-null. I still don't know where the canonical source for that information is though.
Since ASP.NET 5/MVC 6 is not yet officially released and at the moment the API is not yet stable, it isn't that surprising that the documentation has not been completed yet.
Do note that ASP.NET vNext is open source, so where documentation is lacking you can always look at tests to try to work out what to do. Here are some that test the inline constraint area:exists.
[Fact]
public async Task RoutingToANonExistantArea_WithExistConstraint_RoutesToCorrectAction()
{
// Arrange
var server = TestHelper.CreateServer(_app, SiteName, _configureServices);
var client = server.CreateClient();
// Act
var response = await client.GetAsync("http://localhost/area-exists/Users");
// Assert
Assert.Equal(HttpStatusCode.OK, response.StatusCode);
var returnValue = await response.Content.ReadAsStringAsync();
Assert.Equal("Users.Index", returnValue);
}
[Fact]
public async Task RoutingToANonExistantArea_WithoutExistConstraint_RoutesToIncorrectAction()
{
// Arrange
var server = TestHelper.CreateServer(_app, SiteName, _configureServices);
var client = server.CreateClient();
// Act
var response = await client.GetAsync("http://localhost/area-withoutexists/Users");
// Assert
var exception = response.GetServerException();
Assert.Equal("The view 'Index' was not found." +
" The following locations were searched:__/Areas/Users/Views/Home/Index.cshtml__" +
"/Areas/Users/Views/Shared/Index.cshtml__/Views/Shared/Index.cshtml.",
exception.ExceptionMessage);
}
I want to create an HttpPost controller method which takes a string which may include spaces or single quotes. The code I have now looks like:
[HttpPost]
[Route("ValidateExpression")]
public bool ValidateExpression([FromBody] string testExpression )
{
// ...
}
and I post to it via JS (Angular) like:
$http.post(myRootUrl +'ValidateExpression', "'token1' || 'token2'");
I can walk through the JS and see that the test expression is what I expect it to be, and it also looks fine in the body of the outgoing post (as seen in Fiddler & Firebug). But when I breakpoint in the first line of ValidateExpression on the server side, testExpression has been truncated to the first space, and stripped of quotes, so what I receive is token1.
How can I avoid this unwanted transformation?
I guess that I could create a model to wrap a single string, and send my string as field value in a JSON object matching the model...or I could do some escaping on the string, but the first seems ugly/unnecessary and the second seems like I'd be trying to hack around behaviour I don't really understand.
You need to pass in JSON object with key equal to that of parameter name.
// POST api/values
public string Post(string value) {
return value;
}
$.post('/api/values', { value: 'Dave' });
Alternatively, you can satisfy Web API's encoding requirement these ways:
$.post('api/values', "=" + value);
or
$.post('api/values', { '': value });
More information here: http://encosia.com/using-jquery-to-post-frombody-parameters-to-web-api/
Edit:
Also, note that angualrjs http.post and jquery.post behaves differently:
The difference is in how jQuery and AngularJS serialize and transmit
the data. Fundamentally, the problem lies with your server language of
choice being unable to understand AngularJS’s transmission
natively—that’s a darn shame because AngularJS is certainly not doing
anything wrong. By default, jQuery transmits data using Content-Type:
x-www-form-urlencoded and the familiar foo=bar&baz=moe serialization.
AngularJS, however, transmits data using Content-Type:
application/json and { "foo": "bar", "baz": "moe" } JSON
serialization, which unfortunately some Web server languages—notably
PHP—do not unserialize natively.
http://victorblog.com/2012/12/20/make-angularjs-http-service-behave-like-jquery-ajax/