Strange behavior when using dynamic parameter in ASPNET WebApi and json request - asp.net

I have really no good explanation what actually is happening and I hope someone here can explain this behavior.
First I go and create File=>NewProject=>Empty WebApi project.
Then I add one simple controller and host this on IIS.
[RoutePrefix("api")]
public class AccountController : ApiController
{
[HttpPost, Route("account")]
public async Task<IHttpActionResult> Post(dynamic command)
{
dynamic asd = command;
return Ok();
}
}
Then I hit the API with fiddler like this:
POST https://local.com/api/account HTTP/1.1
Content-Type: application/json
{"username":"user1","password":"secret","email":"email#domain.com"}
Then I put a breakpoint inside the controller and using the Immediate Window
command.email
'object' does not contain a definition for 'email' and no extension method 'email' accepting a first argument of type 'object' could be found (are you missing a using directive or an assembly reference?)
BUT using the asd actually works. How is that? Any idea?
asd.email
{email#domain.com}
base: {email#domain.com}
HasValues: false
Type: String
Value: "email#domain.com"
Even more. If I change the dynamic to ExpandoObject I get the result bellow which I actually expect but through asd:
asd.email
"email#domain.com"

Related

React Native: Why does appending query parameters to my .Net API calls automatically return a 404 error?

In my React Native app, I'm trying to hit an endpoint in an API written in .Net. I've got the API code on my local machine, so I'm trying to hit it with a call to fetch(127.0.0.1:5000/api/token). This returns a 404 error. If I instead hit fetch(127.0.0.1:5000/), it returns successfully. The API code hit in this case is HomeController.cs, which contains this:
namespace InTowAPI2.Controllers
{
[AllowAnonymous]
[Route("/a")]
public class HomeController : Controller
{
// GET api/values
[HttpGet]
public string Get()
{
return "Ok";
}
}
}
When I make the call to fetch(127.0.0.1:5000/), it returns "Ok" and hits the breakpoint I set in HomeController.cs. The other file ValuesController.cs that should be hit when I call fetch(127.0.0.1:5000/api/token) contains this code (plus more after it):
namespace InTowAPI2.Controllers
{
[AllowAnonymous]
[Route("/api/[controller]")]
...
...
When I call fetch(127.0.0.1:5000/api/token), the breakpoint in ValuesController.cs is not hit, and it returns a 404 error.
What I tried to do to troubleshoot this was, since HomeController.cs was working, I replaced [Route("/")] with [Route("/api")], and made a call to fetch(127.0.0.1:5000/api). This also returned a 404 error.
What I Want To Know:
Why is it that hitting the API code containing [Route("/")] with fetch(127.0.0.1:5000/) works, but modifying it to hit [Route("/api")] with fetch(127.0.0.1:5000/api) throws a 404 error?
The issue is the routing.
[Route("/api/[controller]")]
This RouteAttribute definition will route the endpoint to the name of the controller.
The other file ValuesController.cs that should be hit...
Seems like you controller name is ValuesController, which would create an endpoint on /api/values, not /api/token.
To fix this, do one of the following things:
Change the name of the controller from ValuesController to TokenController.
Change the RouteAttribute from [Route("/api/[controller]")] to [Route("/api/token")].

Asp.net .core web api versioning does not work for Url path segment

I searched for my problem beforehand in various sources but the answers did not provide me with a solution.
I implemetend Url based web api versioning in a .net core 2.2 project with the way presented here. The version that I used for versioning is the latest Microsoft.AspNetCore.Mvc.Versioning 3.1.2.
I also tried to understand how it works from the following sources: source1, source2, source3, source4.
I am having a ValueController with a GET method in a folder called Initial and a Value2Controller in a folder called New. Both folders are subfolders of the 'Controllers' folder.
The structure is as follows:
The routing in ValueController is
[Route("api/v{version:apiVersion}/[controller]")]
and in Value2Controller is:
[Route("api/v{version:apiVersion}/value")]
I have also set options.EnableEndpointRouting = false; in the Startup.cs and I tried calling api/v1/value or api/v2/value. Both times I get the error: Multiple actions matched. It cannot differentiate between the two controllers actions.
I tried using services.AddApiVersioning(); with no options at all and remove AddVersionedApiExplorer. It does not work. The only thing that works is
putting
[Route("api/v{version:apiVersion}/[controller]")]
in both controllers and make the following api calls:
api/v1/value and api/v2/value2.
The configuration in my startup.cs is as follows:
services.AddApiVersioning(options =>
{
options.ReportApiVersions = true;
options.AssumeDefaultVersionWhenUnspecified = true;
options.DefaultApiVersion = new ApiVersion(1, 0);
options.ApiVersionReader = new UrlSegmentApiVersionReader();
options.UseApiBehavior = true;
});
services.AddVersionedApiExplorer(
options =>
{
options.GroupNameFormat = "'v'VVV";
options.SubstituteApiVersionInUrl = true;
options.AssumeDefaultVersionWhenUnspecified = true;
options.DefaultApiVersion = new ApiVersion(1, 0);
});
What am I missing to call either api/v1/value or api/v2/value and go to the correct request?
After some more debugging, I finally figured out why it wasn't working, so I am posting the solution to anyone who will face a similar problem. The problem was within the Controller Inheritance.
I had created a CustomBaseController (which I had completely disregarded as problematic for some reason) with some methods for global exception handling, the inheritance goes as follows:
[ApiVersionNeutral]
[Route("api/[controller]")]
[ApiController]
CustomBaseController : Controller
and
[ApiVersion("1.0")]
[Route("api/v{version:apiVersion}/[controller]")]
[ApiController]
ValuesController : CustomBaseController { // http method implementations}
[ApiVersion("2.0")]
[Route("api/v{version:apiVersion}/values")]
[ApiController]
ValuesController : CustomBaseController { // updated http method implementations}
The versioning mechanism did not agree with [ApiVersionNeutral] attribute even though it made sense to me that the the base controller would not need to change at all. Moreover I only had the basic routing in the base controller.
Thus I got the error with "Multiple actions matched".
I also found out that the version 1 controller, can inherit the routing from the base controller and had no reason to have a routing there. For all subsequent controllers, the routing must be:
[Route("api/v{version:apiVersion}/values")].
The working solution along with the initial configuration posted above, is the following:
[Route("api/v{version:ApiVersion}/[controller]")]
[ApiController]
CustomBaseController: Controller {}
[ApiVersion("1.0")]
[ApiController]
ValuesController: CustomBaseController { //code }
[ApiVersion("2.0")]
[ApiController]
[Route("api/v{version:ApiVersion}/values")]
Values2Controller: CustomBaseController { //code }
[ApiVersion("3.0")]
[ApiController]
[Route("api/v{version:ApiVersion}/values")]
Values3Controller: CustomBaseController { //code }
Getting values from the following urls:
api/v1/values
api/v2/values
api/v3/values
Even though my issue was resolved, I still don't understand why [ApiVersionNeutral] would cause the routing to not be able to detect the versions of the other controllers correctly. Any explanation would be highly appreciated.
Thank you #Matt Stannett for your comments, they led me to the right direction.

AEM Servlet not getting executed

I have a servlet with OSGI annotation like below
#Component( immediate = true, service = Servlet.class, property = { "sling.servlet.extensions=json",
"sling.servlet.paths=/example/search", "sling.servlet.methods=get" } )
public class SearchSevrlet
extends SlingSafeMethodsServlet {
#Override
protected void doGet( final SlingHttpServletRequest req, final SlingHttpServletResponse resp )
throws ServletException, IOException {
log.info("This is not getting called ");
}
}
But When i try to hit the servlet with JQuery
$.get( "/example/search.json", function( data ) {
$( ".result" ).html( data );
alert( "Load was performed." );
});
I am getting below information rather than servlet getting executed.
{"sling:resourceSuperType":"sling/bundle/resource","servletClass":"com.group.aem.example.servlet.SearchSevrlet","sling:resourceType":"/example/search.servlet","servletName":"com.group.aem.example.servlet.SearchSevrlet"}
Please let me know if i need to make any other configuration.
The info that you are getting is the answer of the Default JSON Servlet
Please read this: Servlets and Scripts
You are registering the "SearchServlet" with the property "sling.servlet.paths". This property is defined as:
sling.servlet.paths: A list of absolute paths under which the servlet is accessible as a Resource. The property value must either be a single String, an array of Strings...
That means that your servlet will be only triggered if you request the same exact path, in this case "/example/search", like this:
GET /example/search
I would recommend you to use the properties "resourceTypes" and "selectors" in your Servlet rather than "paths". For example, a better configuration could be:
property = {
"sling.servlet.resourceTypes=/example/search.servlet",
"sling.servlet.selectors=searchselector",
"sling.servlet.extensions=json",
"sling.servlet.methods=GET"
}
With this config, your SearchServlet should be triggered with a GET request to a resource with resourceType="/example/search.servlet", with the selector "searchselector" and the extension "json". For example:
GET /corcoran/search.searchselector.json
I had a similar problem with yours.
To find out what is wrong, I checked "Recent Requests" page.
(at http://localhost:4502/system/console/requests.)
In my case, there was a log saying, "Will not look for a servlet at (my request path) as it is not in the list of allowed paths".
So I moved to "Config Manager" page(at http://localhost:4502/system/console/configMgr), and searched for "Apache Sling Servlet/Script Resolver and Error Handler".
It has a list named "Execution Paths", and I added my request path to the list.
After adding my path to the list, the problem is solved.

Passing in a date to WebAPI - No HTTP resource was found that matches the request URI

I have a GET request that I make in Chrome Postman. It looks like the following:
http://localhost/WCAPI/Lookup/WCClassDesc/State/AL/Class/7230/DescCode/00/EffDate/2016-04-13
Can anybody see why I would get this response in Postman?
{
"Message": "No HTTP resource was found that matches the request URI 'http://localhost/WCAPI/Lookup/WCClassDesc/State/AL/Class/7230/DescCode/00/EffDate/2016-04-13'.",
"MessageDetail": "No action was found on the controller 'WCClassDesc' that matches the request."
}
My code is:
using System;
using System.Web.Http;
/// <summary>
/// API for loading WCClassDescription which is shown on the PremByClass page.
namespace WCAPI.Controllers.Lookup {
[RoutePrefix("Lookup/WCClassDesc")]
public class WCClassDescController : ApiController {
[Route("State/{State}/Class/{Class}/DescCode/{DescCode}/EffDate/{EffDate}")]
public Models.Lookup.WCClassDesc Get(string ClassState, string ClassCode, string DescCode, DateTime EffDate) {
var desc = (new Premium.BLL.WCClassDesc()).GetCurrentWCClassDesc(ClassState, ClassCode, DescCode, EffDate);
var WC = AutoMapper.Mapper.Map<Models.Lookup.WCClassDesc>(desc);
return WC;
}
}
}
Any help or direction would be greatly appreciated.
Jason
There are multiple problems with your code, let's start from the URI:
Assuming you are testing your application using the root path of your host (localhost) and following your definition of RoutePrefix and Route attributes, the correct URI for your resource is the following:
http://localhost/Lookup/WCClassDesc/State/AL/Class/7230/DescCode/00/EffDate/2016-04-13
That's because there is no WCAPI defined in your RoutePrefix attribute.
The other problem is related to the route parameter mapping, you defined you parameters as {State} and {Class}, but then you are asking for ClassState and ClassCode inside your method.
Rename those parameters to match the ones defined in your route, or else Web API will not map them to your method parameters.

Spring-boot return json and xml from controllers

I have a spring-boot 1.1.7 application that uses Thymeleaf for much of the UI, so the response from my controllers hasn't really been a concern. However, now I need to provide a XML response when a user submits a request via URL.
Here is a typical Request:
http://localhost:9001/remote/search?sdnName=Victoria&address=123 Maple Ave
Here is most of my gradle configuration:
project.ext {
springBootVersion = '1.1.7.RELEASE'
}
dependencies {
compile("org.springframework.boot:spring-boot-starter-web:$springBootVersion")
compile("org.springframework.boot:spring-boot-starter-thymeleaf")
compile("org.springframework.boot:spring-boot-starter-security")
compile("org.springframework.boot:spring-boot-starter-data-jpa:$springBootVersion")
compile("org.springframework.security:spring-security-web:4.0.0.M1")
compile("org.springframework.security:spring-security-config:4.0.0.M1")
compile('org.thymeleaf.extras:thymeleaf-extras-springsecurity3:2.1.1.RELEASE')
compile("org.springframework.boot:spring-boot-starter-actuator")
compile('com.fasterxml.jackson.dataformat:jackson-dataformat-xml:2.5.0')
}
And here is my controller:
#Controller
public class RemoteSearchController {
#Autowired
private SdnSearchService sdnSearchService;
#RequestMapping(value = "/remote/search", method = RequestMethod.GET, produces = MediaType.APPLICATION_XML_VALUE)
public List<Sdn> search(#ModelAttribute SdnSearch sdnSearch) {
List<Sdn> foundSdns = sdnSearchService.find( sdnSearch );
return foundSdns;
}
Here is my Object to be returned:
#Entity
public class Sdn {
#Id
private long entNum;
private String sdnName;
...
//getters & setters here
}
I am able to receive the request via REST client (such as CocoaREST) and handle it. But When I return the list of SDN i get the following exception, even though I do have Jackson & jackson-dataformat-xml on my classpath:
org.springframework.web.HttpMediaTypeNotAcceptableException: Could not find acceptable representation
at org.springframework.web.servlet.mvc.method.RequestMappingInfoHandlerMapping.handleNoMatch(RequestMappingInfoHandlerMapping.java:229)
at org.springframework.web.servlet.handler.AbstractHandlerMethodMapping.lookupHandlerMethod(AbstractHandlerMethodMapping.java:301)
at org.springframework.web.servlet.handler.AbstractHandlerMethodMapping.getHandlerInternal(AbstractHandlerMethodMapping.java:248)
at org.springframework.web.servlet.handler.AbstractHandlerMethodMapping.getHandlerInternal(AbstractHandlerMethodMapping.java:57)
at org.springframework.web.servlet.handler.AbstractHandlerMapping.getHandler(AbstractHandlerMapping.java:299)
My REST Client is including a Accept Header of "text/xml" (but in all honesty I would rather them not have to set this. Ideally any call to this Controller would always get XML, regardless of header being present).
Is there a way to handle this? I thought the Media Converters were included and just returned whatever the controller told them to?
SOLUTION:
See below for the answer I posted.
I had the exact same problem and I found the solution on Spring documentation website : here
In synthesis, I added the following dependency to the pom.xml of my project :
<dependency>
<groupId>com.fasterxml.jackson.dataformat</groupId>
<artifactId>jackson-dataformat-xml</artifactId>
</dependency>
Then I added the following code block to the class that the service had to return :
import javax.xml.bind.annotation.XmlRootElement;
#XmlRootElement
public class Greeting {...}
And it worked.
SOLUTION: I used a combination of both answers below (thank you very much!). I am posting here in case anyone else needs help.
My modified controller:
#Controller
public class RemoteSearchController {
#Autowired
private SdnSearchService sdnSearchService;
#RequestMapping(value = "/remote/search", method = RequestMethod.GET, produces = { "application/xml", "text/xml" }, consumes = MediaType.ALL_VALUE )
#ResponseBody
public SdnSearchResults search(#ModelAttribute SdnSearch sdnSearch) {
List<Sdn> foundSdns = sdnSearchService.find( sdnSearch );
SdnSearchResults results = new SdnSearchResults();
results.setSdns( foundSdns );
return results;
}
}
And on my client, I set the request headers:
Content-type: application/text
Accept: text/xml
I think ultimately the problem was that my client headers were not being set correctly, so I may not have had to make some of these changes. But I liked the idea of a SearchResults class containing a list of results:
#XmlRootElement
public class SdnSearchResults {
private List<Sdn> sdns;
...
}
It may be better to create a new class:
public class SdnSearchResult {
private List<Sdn> sdns;
...
}
Then, a slight change will be required to the existing classes as follows:
public interface SdnSearchService {
SdnSearchResult find(SdnSearch sdnSearch);
}
#Controller
public class UISearchController {
#Autowired
private SdnSearchService sdnSearchService;
#RequestMapping("/search")
public ModelAndView search(#ModelAttribute SdnSearch sdnSearch) {
return new ModelAndView("pages/search/results", "sdns", sdnSearchService.find(sdnSearch).getSdns());
}
}
Once this is done, the other controller must be coded as:
#Controller
public class RemoteSearchController {
#Autowired
private SdnSearchService sdnSearchService;
#RequestMapping("/remote/search")
#ResponseBody
public SdnSearchResult search(#RequestBody SdnSearch sdnSearch) {
return sdnSearchService.find(sdnSearch);
}
}
A quick explanation of the changes from your code:
#RequestBody will automatically deserialize the entire HTTP request body to an SdnSearch instance. External applications will typically submit the request data as HTTP body, so #RequestBody will ensure that the deserialization to Java object happens automatically.
#ResponseBody will automatically serialize the return value according to the external client's capabilities and the libraries available on the classpath. If Jackson is available on the classpath and the client has indicated that they can accept JSON, the return value will be automatically sent as JSON. If the JRE is 1.7 or higher (which means that JAXB is included with the JRE) and the client has indicated that they can accept XML, the return value will be automatically sent as XML.
List<Sdn> needs to be changed to SdnSearchResult to ensure that the application can exchange JSON, XML, RSS and ATOM formats with a single controller method, since XML (and XML based formats) require a root-tag on the output, which a List<Sdn> cannot be translated to.
Once these changes are done, fire up a REST client such as the Postman extension for Chrome and submit a request to /remote/search with the following information:
Request header Accepts set to application/json.
Request header Content-Type set to application/json.
Request body set to the JSON string { "sdnName" : "Victoria", "address" : "123 Maple Ave" }.
This will give you a JSON response.
You've marked the controller method as producing application/xml responses (produces = MediaType.APPLICATION_XML_VALUE). The request's accept header (Accept: text/xml) doesn't match so Spring determines that your search method cannot handle the request.
There are a few different ways to fix this on the server, depending on your exact requirements:
You could remove the produces attribute entirely
You could specify multiple media types: produces = { "application/xml", "text/xml" }
I am not sure about your version of Spring Boot (1.1.7.RELEASE) but I am on version 1.5.2.RELEASE and this xml conversion / serialization happens automatically without usage of any jackson dependencies as mentioned in few of the answers.
I guess that is happening because org.springframework.http.converter.xml.Jaxb2RootElementHttpMessageConverter is automatically configured since Spring Boot version 1.5.1.RELEASE & that converter uses default JAXB implementation of JRE ( so no explicit xml conversion dependency needed ) .
Second, Accept header set by clients in request decides which format the output is expected so a request mapping like below ( i.e. a single end point ) ,
#RequestMapping(method = RequestMethod.GET, value = "/remote/search", produces = {
MediaType.APPLICATION_JSON_VALUE, MediaType.APPLICATION_XML_VALUE, MediaType.TEXT_XML_VALUE })
can be used to produce an xml as well as a JSON response ( if Accept header is set as text/xml or application/xml & application/json respectively.
Note 1 : javax.xml.bind.annotation.XmlRootElement needs to be specified on root class if xml response is expected for a Java class. This is mandatory.
Note 2 : Jackson for json is already included in Spring Boot so that is not to be explicitly included for json outputs
Note 3 : Accept header - Output match off happens automatically by framework & developer doesn't have to code anything specific for that.
So in my opinion, if you only add XmlRootElement to your base class & upgrade your Spring Boot version, your server side is all set. Responsibility to set correct Accept header lies with the clients.
In addition to what Michael told in his answer, I added the following dependencies as well to pom.xml
<dependency>
<groupId>org.codehaus.woodstox</groupId>
<artifactId>woodstox-core-asl</artifactId>
<version>4.4.1</version>
</dependency>
For some reason, the jackson-dataformat-xml alone was not helping.
I also made sure that ResponseEntity is returned in the get call and removed the produces=MediaType from the RequestMapping annotation.
With these changes, I was able to get the correct data but I had to give the extension of mime type to the REST URL during get call. ie, specify explicitly like: http://localhost:8080/hello.xml or http://localhost:8080/hello.json in browser
In my case I wanted to return a formatted XML string and it was all combined into one line.
Adding produces = { "application/xml", "text/xml" } to the request mapping was enough to return the string as formatted XML (with indentation).
example:
#RequestMapping(method= RequestMethod.GET, value="/generate/{blabla}", produces = { "application/xml", "text/xml" })
public String getBlaBla(#PathVariable("param") String param) throws IOException {
}
Goodluck.

Resources