swagger-ui: How to add a header-param request to every-api - swagger-ui

I am new to quarkus and have a bit familiar with swagger-ui. I am able to add a #Parameter to the an endpoint like this:
#Parameter(in = ParameterIn.HEADER, required = true, name = "my-header-id")
But, I would like to add this param to every endpoint. How can I achieve this?
I am using quarkus-smallrye-openapi for the ui.

You can specify parameters on method or class level. If you define the param as class field, then it will be added to all methods of the corresponding endpoint:
#Path("/someendpoint")
public class MyEndpoint {
#HeaderParam("my-header-id")
#Parameter(name = "my-header-id")
String myHeaderId;
#GET
public Response getAll() {return Response.ok().build()}
#GET
#Path("{id}")
public Response someMethod(#PathParam("id") String id) {return Response.ok().build();}
}

Related

Attribute routing for two actions leads to a "Not valid OData path template"

So i have two functions that return a customer, which get feeded by two different parameters. One being the ID of the customer and the other being his customer number.
My controller:
using System.Linq;
using System.Net;
using System.Web.Http;
using System.Web.OData;
using System.Web.OData.Routing;
using Models;
using AutoMapper;
using AutoMapper.QueryableExtensions;
using System.Web.OData.Extensions;
using Importing;
using Objects;
using Microsoft.OData;
namespace Controllers
{
public class CustomersController : ODataController
{
// GET: CustomerByCNO(5)
[HttpGet]
[ODataRoute("CustomerByCNO({key})")]
[EnableQuery]
public SingleResult<CustomerDTO> GetCustomerByCNO([FromODataUri]string key)
{
Import i = new Import();
var customer = i.GetCustomer(key).ProjectTo<CustomerDTO>().AsQueryable();
return SingleResult.Create(customer);
}
// GET: Customer(5)
[HttpGet]
[ODataRoute("Customer({id})")]
[EnableQuery]
public SingleResult<CustomerDTO> Get([FromODataUri]int id)
{
Import i = new Import();
var customer = i.GetCustomer(id).ProjectTo<CustomerDTO>().AsQueryable();
return SingleResult.Create(customer);
}
}
}
Initialization:
using AutoMapper;
using Models;
using Objects;
using System.Web.Http;
using System.Web.OData.Builder;
using System.Web.OData.Extensions;
using Microsoft.OData.Edm;
namespace API
{
public static class WebApiConfig
{
public static void ConfigureAPI(HttpConfiguration config)
{
config.MapODataServiceRoute(
routeName: "odata",
routePrefix: "",
model: GetEdmModel()
);
config.EnsureInitialized();
}
private static IEdmModel GetEdmModel()
{
ODataConventionModelBuilder builder = new ODataConventionModelBuilder
{
Namespace = "Controllers",
ContainerName = "DefaultContainer"
};
builder.EntitySet<CustomerDTO>("Customer")
.EntityType.HasKey(c => c.Id)
.CollectionProperty(c => c.CustomFields);
var edmModel = builder.GetEdmModel();
return edmModel;
}
}
}
While the second functions works as intended the first functions does not and the EnsureInitialized() function throws an InvalidOperationException saying, that it is no valid OData path template and that no resource has been found. How can i get this to work? Not quite sure what i am missing here.
UPDATE 1:
Changing the Controller method to this:
[HttpGet]
[ODataRoute("CustomerByNo(No={no})")]
public SingleResult<CustomerDTO> CustomerByNo([FromODataUri] int no)
{
Import i = new Import();
var customer = i.GetCustomer(no.ToString()).ProjectTo<CustomerDTO>().AsQueryable();
return SingleResult.Create(customer);
}
with this additional line in the config:
builder.Function("CustomerByNo").Returns<SingleResult<CustomerDTO>>().Parameter<int>("No");
Made it so i can access the functions at least. I had to change the parameter to an int as well, seems like it doesnt like strings? However the return value is not deserialized and shown as usual. Also if i leave the [EnableQuery] line in the method declaration, the call will crash saying that it doesnt know how to deserialize since it is not bound to the entityset of Customer i guess.
Trying it this way however, leads to the original error message, that the resource could not be found:
builder.EntityType<CustomerDTO>().Collection.Function("CustomerByNo").Returns<SingleResult<CustomerDTO>>().Parameter<int>("No");
You have to declare your custom odata functions in the convention model:
FunctionConfiguration customerByCNOFunction = builder.Function("CustomerByCNO");
customerByCNOFunction.Returns<CustomerDTO>();
customerByCNOFunction.Parameter<string>("key");
Update :
My first answer was for declaring a functions that returns a type not queryable in odata.
To enable query, the function needs to return an odata entity from an entity set :
builder.Function("CustomerByNo").ReturnsFromEntitySet<CustomerDTO>("Customer").Parameter<int>("No")

How to ignore #FeignClient apis from being processed by swagger?

I am using swagger and Feign in one project, and the swagger will take #RequestMapping annotated methods and create the documentation. But this is weird to do so for classes and methods annotated by both #FeightClient and #RequestMapping. So how to ignore these apis in swagger? Which class of swagger
do the scan job so that I could learn and add some other class to ignore these apis annotated by #FeightClient?
#FeignClient(name = TodoItemRpcRepository.SERVICE_NAME)
#RequestMapping("/api/todos")
public interface TodoItemRpcRepository {
#RequestMapping(value = "/{id}", method = RequestMethod.GET)
TodoItem findById(#RequestHeader("X-Auth-Token") final String token, //
#PathVariable("id") final Long id);
}
In your docket select you can specify a predicate. You could use the withClassAnnotation method to specify #FeignClient as the annotation. You'd need to combine it with the Predicates.not to ignore in your case.
I removed the #RequestMapping over the #FeignClient annotated class and add a path attribute in the #FeignClient. This time, issue was resolved perfectly. I guess #RequestMapping is not permitted to #FeignClient annotated class.
#FeignClient(name = TodoItemRpcRepository.SERVICE_NAME, name="/api/todos")
public interface TodoItemRpcRepository {
#RequestMapping(value = "/{id}", method = RequestMethod.GET)
TodoItem findById(#RequestHeader("X-Auth-Token") final String token, //
#PathVariable("id") final Long id);
}

How Data is posted (POST) to Service in Servicestack , is it through URL?

I have complex requstDto which composed of other list of DTO's (Entity framework Entities) like
[Route("/demoservice/{Userdemo}/{EmployerDemoid}/{ReportDemo}/{DemoselectedDataList}/", "POST")]
public class ReportDemo : IReturn<String>
{
public List<selectedidList> selectedDataList{ get; set; }
}
where UserReport is follows
public class UserReport
{
public string UserName { get; set; }
public Datetime CreatedON{ get; set; }
}
when i try to post to request it gives me following error
A potentially dangerous Request.Path value was detected from the client (:)
i think it gives error due to : in CreatedON field ( for time part).
is the post values are also sent through URL to ServiceStack URL ? if yes
1) what if we have very large and complex requestDTO resulting into large number of characters (greater than allowed )in URL?
2) how to make above scenario work as ":" is reserved and cant be sent through URL?
3) How to see request URL Generated from client ?
My Client code in MVC.net is
var client = new JsonServiceClient(ConfigurationManager.AppSettings["applicationUrl"])
{
//for windows authentication
Credentials = CredentialCache.DefaultCredentials
};
var result = client.Post (new ReportDemo
{
UserName = model.UserName,
EmployerID = model.EmployerID,
Report = model.Report,
selectedDataList =userReportViewModel.selectedDataList
});
Thanks in advance,
Amol
Only the /path/info of the Url should be specified in the [Route]. Ideally routes should use a human-readable logically-structured Url that refers to a "Resource" (noun). See the SeviceStack REST Events Example for different examples.
Routes should also never include complex types and any variable that isn't on the [Route] is automatically sent in the HTTP Request Body for POST requests or the QueryString from GET Requests.
For a User Report like this I would choose a URL that identifies the report, if the report has a name like "Demo Report" I would use a path info like:
[Route("/reports/demo")]
public class ReportDemo : IReturn<String> { ... }
Otherwise if this is a Report for Users you may instead want to use something like:
[Route("/users/{UserName}/reports/demo")]
public class ReportDemo : IReturn<String> { ... }
You can check what url is used by using the Reverse Routing Extension methods, e.g:
var request = ReportDemo { UserName = "Foo", ... };
request.ToPostUrl().Print(); //= /users/Foo/reports/demo
Now you can send your Request with any property not in the Route getting POST'ed to the above url, e.g:
string result = client.Post (new ReportDemo {
UserName = userReportViewModel.UserName,
EmployerID = userReportViewModel.EmployerID,
Report = userReportViewModel.Report,
selectedDataList =userReportViewModel.selectedDataList
});
If your Report does return a string you can use IReturn<string> however if it returns a Response DTO you'll want to use that instead, e.g IReturn<ReportDemoResponse>.

How to get GET parameters with ASP.NET MVC ApiController

I feel a bit absurd asking this but I can't find a way to get parameters for a get request at
/api/foo?sort=name for instance.
In the ApiController class, I gave a public string Get(). Putting Get(string sort) makes /api/foo a bad request. Request instance in the ApiController is of type System.Net.Http.HttpRequestMessage. It doesn't have a QueryString or Parameters property or anything.
The ApiController is designed to work without the HttpContext object (making it portable, and allowing it to be hosted outside of IIS).
You can still access the query string parameters, but it is done through the following property:
Request.GetQueryNameValuePairs()
Here's an example loop through all the values:
foreach (var parameter in Request.GetQueryNameValuePairs())
{
var key = parameter.Key;
var value = parameter.Value;
}
You could just use
HttpContext.Current.Request.QueryString
Here's an example that gets the querystring q from the request and uses it to query accounts:
var q = Request.GetQueryNameValuePairs().Where(nv => nv.Key =="q").Select(nv => nv.Value).FirstOrDefault();
if (q != null && q != string.Empty)
{
var result = accounts.Where(a=>a.Name.ToLower().StartsWith(q.ToLower()));
return result;
}
else
{
throw new Exception("Please specify a search query");
}
This can be called then like this:
url/api/Accounts?q=p
Get all querystring name/value pairs into a variable:
IEnumerable<KeyValuePair<string, string>> queryString = request.GetQueryNameValuePairs();
Then extract a specified querystring parameter
string value = queryString.Where(nv => nv.Key == "parameterNameGoesHere").Select(nv => nv.Value).FirstOrDefault();
You can also use the following
var value = request.GetQueryNameValuePairs().Where(m => m.Key == "paramName").SingleOrDefault().Value;
if we have a proper model for that request
for example
public class JustModel
{
public int Id {get;set;}
public int Age {gets;set;}
}
and query like this
/api/foo?id=1&Age=10
You could just use [FromUri] attribute
For example
public IHttpActionResult GetAge([FromUri] JustModel model){}
You're trying to build an OData webservice? If so, just return an IQueryable, and the Web API will do the rest.
Adding a default value does the job:
public string Get(string sort="")

How to get and set http headers in an Action, the testable way

I have an action that returns either a FileContentResult or a NotModifiedResult, which is a custom result type that returns HTTP 304 to indicate that the requested resource has not been modified, like this:
[ReplaceMissingPicture(Picture = "~/Content/Images/nothumbnail.png", MimeType = "image/png")]
public ActionResult Thumbnail(int id)
{
var item = Service.GetItem(id);
var requestTag = Request.Headers["If-None-Match"] ?? string.Empty;
var tag = Convert.ToBase64String(item.Version.ToArray());
if (tag == requestTag)
{
return new NotModifiedResult();
}
if (item.Thumbnail != null)
{
var thumbnail = item.Thumbnail.ToArray();
var mime = item.PictureMime;
Response.AppendHeader("ETag", tag);
return File(thumbnail, mime);
}
else
{
return null;
}
}
This action needs to access the Response object, which is of course not present during testing, so that makes this action untestable. I could add conditional statements around it, so that it runs during testing, but then I can't test for the headers being set correctly.
What would be a solution to this problem?
FYI, the ReplaceMissingPicture filter returns a specific resource in case null was returned from this action, to keep the MapPath() call out of the controller for the very same reason.
The first step would be to create an interface which simplifies the services you need:-
public interface IHeaders
{
public string GetRequestHeader(string headerName);
public void AppendResponseHeader(string headerName, string headerValue);
}
Now create a default implementation:-
public Headers : IHeaders
{
public string GetRequestHeader(string headerName)
{
return HttpContext.Current.Request[headerName];
}
public void AppendResponseHeader(string headerName, string headerValue)
{
HttpContext.Current.Response.AppendHeader(headerName, headerValue);
}
}
Now add a new field to your Controller:-
private IHeaders myHeadersService;
add new constructor to you controller:-
public MyController(IHeaders headersService)
{
myHeadersService = headersService;
}
modify or add the default constructor:-
public MyController()
{
myHeadersService = new Headers();
}
now in your Action code use myHeadersService instead of the Response and Request objects.
In your tests create your own implementation of the IHeaders interface to emulate/test the Action code and pass that implementation when constructing the Controller.
How about creating a subclass of FileResult--say ETagFileResult--that in its ExecuteResult() method sets the ETag header, and then defaults to the base class implementation? You can test that class with a mocked context (as you presumably are with your NotModifiedResult) to be sure that it's doing the right thing. And remove the entire complication from the testing of the controller.
Failing that, it's possible to set a mocked context on the controller in your test (after instantiating the class, before calling the action method). See this question, for instance. But that seems like more work.
(Also, by the way, it looks like you're quoting the tag value twice there: once when tag is set, and once more when you actually set the header....)

Resources