SpringDoc Problem With Methods With Different Query Parameters - spring-restcontroller

I want to do something like create two method for same url pattern with different arguments
#RequestMapping(value = "/searchUser", params = "userID")
public String searchUserById(#RequestParam long userID, Model model) {
// ...
}
#RequestMapping(value = "/searchUser", params = "userName")
public ModelAndView searchUserByName(#RequestParam String userName) {
// ...
}
Spring supports this and it works fine. SpringDoc does not. It creates a single endpoint with 2 parameters.Is this a known issue?

This is supported, but you will have to use swagger #Operation to describe your methods in one path.
One of the OpenAPI 3 rules.
Two POST methods for the same path are not allowed – even if they have different parameters (parameters have no effect on uniqueness).
More details are available on OpenAPI 3 site.
This has been explained here: https://github.com/springdoc/springdoc-openapi/issues/580

Related

Generate Link To Spring Data Rest Controller

I created a REST API with Spring Data Rest that forks fine. It must be possible to clone Projects via the API, so I added a custom #RestController to implement that via POST /projects/{id}/clone.
#RestController
#RequestMapping(value = "/projects", produces = "application/hal+json")
#RequiredArgsConstructor(onConstructor = #__(#Autowired))
public class ProjectCloneController {
private final ProjectRepo projectRepo;
#PostMapping("/{id}/clone")
public EntityModel<Project> clone(#PathVariable String id) {
Optional<Project> origOpt = projectRepo.findById(id);
Project original = origOpt.get();
Project clone = createClone(original);
EntityModel<Project> result = EntityModel.of(clone);
result.add(linkTo(ProjectRepo.class).slash(clone.getId()).withSelfRel());
return result;
}
I am stuck at the point where I need to add a Link to the EntityModel that points to an endpoint provided by Spring Data Rest. It will need to support a different base path, and act correctly to X headers as well.
Unfortunately, the line above (linkTo and slash) just generates http://localhost:8080/636f4aaac9143f1da03bac0e which misses the name of the resource.
Check org.springframework.data.rest.webmvc.support.RepositoryEntityLinks.linkFor

what is the usage of name Property in HttpGet ( such as [HttpGet("/products2/{id}", Name = "Products_List")])

In asp.net core, I seen
[HttpGet("/products2/{id}", ***Name = "Products_List")]***
public IActionResult GetProduct(int id)
{
return ControllerContext.MyDisplayRouteInfo(id);
}
what is the usage of name Property in HttpGet (such as[HttpGet("/products2/{id}", Name = "Products_List")])
And, How Can I read/send a Multipart/form-data from/to an apiapicontroller/client?
Yes, it can be used like this. The second parameter of Url.Link is an object.
#Url.Link("Products_List", new { id = 1 })
Also this property RouteUrl can use it.
#Url.RouteUrl("Products_List",new { id=2})
About route name, this is the official introduction:
The route names give the route a logical name. The named route can be used for URL generation. Using a named route simplifies URL creation when the ordering of routes could make URL generation complicated. Route names must be unique application wide.
Route names:
Have no impact on URL matching or handling of requests.
Are used only for URL generation.
If you send a Multipart/form-data. The apicontroller can get it with FromForm.
[HttpGet("routepath")]
public IActionResult get([FromForm]SampleModel model)
{
//...
}
I found that can be used to string uri = Url.Link(“ Products_List”, id = 1);
Is there Some one can give me more detailed information?

Efficient way to bring parameters into controller action URL's

In ASP.Net Core you have multiple ways to generate an URL for controller action, the newest being tag helpers.
Using tag-helpers for GET-requests asp-route is used to specify route parameters. It is from what I understand not supported to use complex objects in route request. And sometimes a page could have many different links pointing to itself, possible with minor addition to the URL for each link.
To me it seems wrong that any modification to controller action signature requires changing all tag-helpers using that action. I.e. if one adds string query to controller, one must add query to model and add asp-route-query="#Model.Query" 20 different places spread across cshtml-files. Using this approach is setting the code up for future bugs.
Is there a more elegant way of handling this? For example some way of having a Request object? (I.e. request object from controller can be put into Model and fed back into action URL.)
In my other answer I found a way to provide request object through Model.
From the SO article #tseng provided I found a smaller solution. This one does not use a request object in Model, but retains all route parameters unless explicitly overridden. It won't allow you to specify route through an request object, which is most often not what you want anyway. But it solved problem in OP.
<a asp-controller="Test" asp-action="HelloWorld" asp-all-route-data="#Context.GetQueryParameters()" asp-route-somestring="optional override">Link</a>
This requires an extension method to convert query parameters into a dictionary.
public static Dictionary GetQueryParameters(this HttpContext context)
{
return context.Request.Query.ToDictionary(d => d.Key, d => d.Value.ToString());
}
There's a rationale here that I don't think you're getting. GET requests are intentionally simplistic. They are supposed to describe a specific resource. They do no have bodies, because you're not supposed to be passing complex data objects in the first place. That's not how the HTTP protocol is designed.
Additionally, query string params should generally be optional. If some bit of data is required in order to identify the resource, it should be part of the main URI (i.e. the path). As such, neglecting to add something like a query param, should simply result in the full data set being returned instead of some subset defined by the query. Or in the case of something like a search page, it generally will result in a form being presented to the user to collect the query. In other words, you action should account for that param being missing and handle that situation accordingly.
Long and short, no, there is no way "elegant" way to handle this, I suppose, but the reason for that is that there doesn't need to be. If you're designing your routes and actions correctly, it's generally not an issue.
To solve this I'd like to have a request object used as route parameters for anchor TagHelper. This means that all route links are defined in only one location, not throughout solution. Changes made to request object model automatically propagates to URL for <a asp-action>-tags.
The benefit of this is reducing number of places in the code we need to change when changing method signature for a controller action. We localize change to model and action only.
I thought writing a tag-helper for a custom asp-object-route could help. I looked into chaining Taghelpers so mine could run before AnchorTagHelper, but that does not work. Creating instance and nesting them requires me to hardcode all properties of ASP.Net Cores AnchorTagHelper, which may require maintenance in the future. Also considered using a custom method with UrlHelper to build URL, but then TagHelper would not work.
The solution I landed on is to use asp-all-route-data as suggested by #kirk-larkin along with an extension method for serializing to Dictionary. Any asp-all-route-* will override values in asp-all-route-data.
<a asp-controller="Test" asp-action="HelloWorld" asp-all-route-data="#Model.RouteParameters.ToDictionary()" asp-route-somestring="optional override">Link</a>
ASP.Net Core can deserialize complex objects (including lists and child objects).
public IActionResult HelloWorld(HelloWorldRequest request) { }
In the request object (when used) would typically have only a few simple properties. But I thought it would be nice if it supported child objects as well. Serializing object into a Dictionary is usually done using reflection, which can be slow. I figured Newtonsoft.Json would be more optimized than writing simple reflection code myself, and found this implementation ready to go:
public static class ExtensionMethods
{
public static IDictionary ToDictionary(this object metaToken)
{
// From https://geeklearning.io/serialize-an-object-to-an-url-encoded-string-in-csharp/
if (metaToken == null)
{
return null;
}
JToken token = metaToken as JToken;
if (token == null)
{
return ToDictionary(JObject.FromObject(metaToken));
}
if (token.HasValues)
{
var contentData = new Dictionary();
foreach (var child in token.Children().ToList())
{
var childContent = child.ToDictionary();
if (childContent != null)
{
contentData = contentData.Concat(childContent)
.ToDictionary(k => k.Key, v => v.Value);
}
}
return contentData;
}
var jValue = token as JValue;
if (jValue?.Value == null)
{
return null;
}
var value = jValue?.Type == JTokenType.Date ?
jValue?.ToString("o", CultureInfo.InvariantCulture) :
jValue?.ToString(CultureInfo.InvariantCulture);
return new Dictionary { { token.Path, value } };
}
}

Method parameter annotated with #FormParam is always null in JAX-RS

I am trying to send multiple form parameters to my REST servive using POST. But the parameters sent by the client are always received as null.
#POST
#Path("/login")
#Produces({ "application/json" })
public LoginData userLogin(#FormParam("picture") String picture,
#FormParam("name") String name,
#FormParam("email") String email) {
...
}
When I remove all the parameters like the code below, it works properly:
#POST
#Path("/login")
#Produces({ "application/json" })
public LoginData userLogin() {
...
}
I've checked and the values sent by the client are not null.
Is there a different way to receive the parameters?
Annotate your method with #Consumes(MediaType.APPLICATION_FORM_URLENCODED):
#POST
#Path("/login")
#Produces(MediaType.JSON)
#Consumes(MediaType.APPLICATION_FORM_URLENCODED)
public LoginData userLogin(#FormParam("picture") String picture,
#FormParam("name") String name,
#FormParam("email") String email) {
...
}
And ensure the Content-Type of the request is application/x-www-form-urlencoded.
This might not be useful for the actual question. Might be useful for some people who has similar issue along with consumes annotation.
I had the similar issue even if I have the annotation it was null.
The reason was like my project was using both org.codehaus.jackson library and com.fasterxml.jackson libraries. So, mapping had issues.
Once I updated the parent and related child projects to fasterxml, the formparam annotation was working fine.
So search your project and update all the references in pom.xml and java imports from org.codehaus to com.fasterxml. FormParam and HeaderParam null issue will be resolved.

Specifying route values for OData with Web API

I'm working on a new OData project, and am trying to do it with Web API 2 for the first time. The OData feed was pretty simple to put in place, which was great in comparison to WCF.
The problem I have now is that my OData feed will be used in a "multi-tenant" environment and I would like to use "friendly" URLs for the feed depending on the tenant. Therefore, I would ideally need the feed URLs to look like this:
/store/tenant1/Products
/store/tenant2/Products
Both URLs are pointing to the same controller and ultimately the same dataset, but I would like to enforce some entity filtering based on the tenant. Apparently this is going to be difficult and somewhat different to standard Web API routing since I can only specify a route prefix and not a route template.
So far, I've modified my OData controller to take the tenant name as a parameter and this works great when hitting the following url (which is not exactly what I want, see target above):
http://mydomainname/odata/Products?tenantName=test
Using this route definition:
ODataConventionModelBuilder modelBuilder = new ODataConventionModelBuilder();
modelBuilder.EntitySet<Product>("Products");
IEdmModel model = modelBuilder.GetEdmModel();
config.Routes.MapODataRoute(routeName: "OData", routePrefix: "odata", model: model);
And this is the sample action on my controller:
[Queryable]
public IQueryable<Product> GetPproducts(string tenantName)
{
return _products.Where(p=>p.TenantName == tenantName);
}
I'm not quite sure if this is possible and my last resort will be to use URL rewrite rules, but I'd rather avoid this and have everything in code, done the right way.
Thanks a lot for your help!
After some investigation I found it works in this way: Just apply the route prefix name to the query, for example:
public class MoviesController : ODataController
{
private MoviesContext _db = new MoviesContext();
public IHttpActionResult Get()
{
var routeName=Request.ODataProperties().RouteName;
ODataRoute odataRoute=Configuration.Routes[routeName] as ODataRoute;
var prefixName = odataRoute.RoutePrefix;
return Ok(_db.Movies.Where(m=>m.Title.StartsWith(prefixName)));
}
// Other methods here
}
Note: The above code is based on ODataActionsSample in https://aspnet.codeplex.com/SourceControl/latest#Samples/WebApi/OData/v4/
Now OData v4 has become a standard of OASIS, but v3 is not, so v4 seems a good start point.