Does anyone have any suggestions (or a regular expression) for parsing the HTTP Accept header?
I am trying to do some content-type negotiation in ASP.NET MVC. There doesn't seem to be a built in way (which is fine, because there are a lot of schools of thought here), but the parsing is not entirely trivial and I would rather not re-invent the wheel if someone has already done it well and is willing to share.
Have you seen this article? It gives a pretty comprehensive implementation for parsing the Accept header and subsequently doing something useful with it.
As of .NET 4.5 (I think—Microsoft have made info on framework versions < 4.5 rather obscure these days), you can use one of the the built in parsers from System.Net.Http.Headers:
public IOrderedEnumerable<MediaTypeWithQualityHeaderValue> GetMediaTypes(string headerValue) =>
headerValue?.Split(',')
.Select(MediaTypeWithQualityHeaderValue.Parse)
.OrderByDescending(mt => mt.Quality.GetValueOrDefault(1));
Then you can do something like this:
var headerValue = "application/json, text/javascript, */*; q=0.01";
var mediaTypes = GetMediaTypes(headerValue);
Giving you a nice list of all the media types, where the preferred option is the first item. Here's a LINQPad Dump of the mediaTypes result from the example:
Hat tip to this answer, for getting me on the right track.
I've written a parser in PHP. It's not complex, but it will give you an array of mime types in order of preference.
Found another implementation in php here
After reading the xml.com article I decided to not write a function for the Accept header myself ;)
Fortunately the article points to a good library: https://code.google.com/p/mimeparse/ - in my case I need it as a Node.js module: https://github.com/kriskowal/mimeparse
Building on https://stackoverflow.com/a/49011308/275501 from https://stackoverflow.com/users/43140/mark-bell above:
public class MyController : Controller
{
[HttpGet]
[Route("/test")]
public ActionResult Index() {
// does this request accept HTML?
var acceptsHTML = IsAcceptable("text/html");
var model = FetchViewModel();
return acceptsHTML ? (ActionResult) View(model) : Ok(model);
}
private bool IsAcceptable(string mediaType) =>
Request.Headers["Accept"].Any(headerValue =>
!string.IsNullOrWhiteSpace(headerValue) &&
headerValue.Split(",").Any(segment => MediaTypeHeaderValue.Parse(segment).MediaType == mediaType));
private object FetchViewModel() {
return new { Description = "To be completed" };
}
}
The RFC is quite complex. If the regex where to follow these rules to the letter, it would become several lines long.
If you already have the Accept-header, and ignore the quotes and the parameters, you could do something like this to match each pair:
/([^()<>#,;:\\"\/[\]?={} \t]+)\/([^()<>#,;:\\"\/[\]?={} \t]+)/
* is included in the character class, so it does not need any special case in the regex.
Related
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/
Given the following ASP.NET WebAPI, I am trying to send a test POST using Fiddler, but can't get it to work. Whatever I send, I always just see the No data sent to service message.
Imports System.Web.Http
Imports System.Net.Http
Imports System.Net
Namespace HelloWebApiDemo
Public Class MyApiController
Inherits ApiController
Public Function [Get]() As HttpResponseMessage
Return Request.CreateResponse(HttpStatusCode.OK, "Hello")
End Function
Public Class MyXmlData
Public Property UserName As String
Public Property Password As String
Public Property SomeData As String
End Class
Public Function Post(<FromBody> x As MyXmlData) As HttpResponseMessage
If x Is Nothing Then
Return Request.CreateResponse(HttpStatusCode.InternalServerError, "No data sent to service")
End If
Dim u As String = String.Empty
Dim p As String = String.Empty
Dim d As String = String.Empty
If Not String.IsNullOrEmpty(x.UserName) Then
u = x.UserName
End If
If Not String.IsNullOrEmpty(x.Password) Then
p = x.Password
End If
If Not String.IsNullOrEmpty(x.SomeData) Then
d = x.SomeData
End If
Return Request.CreateResponse(HttpStatusCode.OK, String.Format("You posted {0}, {1} with a string that is {2} characters in length", u, p, d.Length.ToString))
End Function
End Class
End Namespace
In Fiddler, my POST looks like this:
Can anyone please advise on what I'm doing wrong? I used Content-Type: text/xml hoping that ASP.NET would be able to decipher it correctly.
Update
New screen grab following input:
Keep the Content-Type: text/xml request header and change the XML to like this.
<MyApiController.MyXmlData
xmlns:i="http://www.w3.org/2001/XMLSchema-instance"
xmlns="http://schemas.datacontract.org/2004/07/HelloWebApiDemo.HelloWebApiDemo">
<Password>somepassword</Password>
<SomeData>somedata</SomeData>
<UserName>bob</UserName>
</MyApiController.MyXmlData>
Final XML snippet that was successfully deserialized:
<MyApiController.MyXmlData
xmlns:i="http://www.w3.org/2001/XMLSchema-instance"
xmlns="http://schemas.datacontract.org/2004/07/_WebApplication1.HelloWebApiDemo">
<Password>Password</Password>
<SomeData>Data here</SomeData>
<UserName>Some Username</UserName>
</MyApiController.MyXmlData>
_WebApplication1 is the name of the solution. That must've been what was missing.
For those who have the ability to do this when faced with the same issue, another option is to use JSON. I am a huge fan of XML, but unfortunately, it seems things are far more stacked against XML's usage in scenarios like this, because in this case, the standard WebApi parser requires all that hideous extra stuff, including intimate knowledge of backend namespaces and types, as well as a full XML spec namespace (* see rant below).
{
"SomeData": "R2D2",
"UserName": "Johny",
"Password": "password",
"Num": 1013,
"IsCool": true
}
*( Rant, please ignore if you want to: Why require this, oh ye good gentleman who made WepApi and other such frameworks? why not allow the XML to be liberated, freely receivable without all this 'ugly'? If included, it could still specify something useful, if one needs to, but why must the rest of us include this ugliness? It seems things have been (arbitrarily, though perhaps more for historical baggage reasons, sorry you poor XML creature, you're stuck with yesterday's baggage though it need not be) stacked against XML's usage, including that, even with all this, a standard parser likely would not allow you to post the XML with the values as XML attributes. )
You can mark your entity class with DataContract attribute setting namespace to an empty string:
<DataContract(Namespace := "")>
Public Class MyXmlData
End Class
After that xmlns parameters will not be necessary.
I'm just starting to play with NancyFx to compare it with the .net MVC WebAPI stuff and I've hit an issue straight away. As I understand it Nancy should handle serialization straight out of the box. But I can't seem to get it working.
I have a Nancy Module that looks like this:
public class HelloWorld : NancyModule
{
public HelloWorld()
{
Post["/"] = parameters =>
{
var myFieldValue = parameters.myField;
return HttpStatusCode.OK;
};
}
}
And I post to it using Fiddler like this:
Headers:
User-Agent: Fiddler
Content-Type: application/json
Host: localhost:3141
Content-Length: 24
Request-Body:
{"myField" : "profit"}
However when the parameters object is empty (and so, therefore is the myFieldValue object).
I'm sure I've missed something really obvious, but I don't know what!
Parameters are for captures in the url (e.g. /foo/{bar} would capture a bar variable in parameters. If you are posting JSON you should use the model binder (var foo =this.Bind();
I would recommend taking a look at the docs too.. All of this is covered there :-)
For posting data you have to use model binding.
Unfortunately, model binding to dynamic is not supported and you have to create request classes. There is a proposed workaround , but I didn't use it myself.
If you don't want to define class for particular request and use model binding, then you can use power of dynamic with json.net.
Example:
public AuthTokenModule (IAuthService authService, UserMapper mapper) : base ("api/v1/authToken")
{
Post ["/login"] = x => {
dynamic request = JsonConvert.DeserializeObject (Request.Body.AsString ());
var user = mapper.ValidateUser ((string)request.email, (string)request.password);