Our team opted to start by defining a yaml file first and generating server stubs using swagger code-gen.
Question 1
Is this the standard way to design asp.net core 2 APIs?
Question 2
How can we just expose extended controller. Say we use swagger generated api controller as base class and override the existing method in a different file so that we can keep generated code untouched. When I try to host this project using swachbuckle I end up getting "System.NotSupportedExceptioL: HTTP method "POST" & path overloaded by actions".
public class BaseApiController : Controller
{
[HttpGet]
[Route("/api/data")]
[SwaggerOperation("GetData")]
[Swashbuckle.AspNetCore.SwaggerGen.SwaggerResponse(200, type: typeof(List<Data>))]
public virtual IActionResult GetData()
{
string exampleJson = null;
var example = exampleJson != null
? JsonConvert.DeserializeObject<List<Data>>(exampleJson)
: default(List<Tag>);
return new ObjectResult(example);
}
}
public class ChildApiController : BaseApiController
{
public override IActionResult GetData()
{
return base.GetData();
}
}
Related
I am new the API in general, let me give you the background of the API and what I want it to do.
I have a API have that are external facing and so every incoming request are required to check the signature from header. literality my code in every controller call are checking the signature and created many duplicated code.
my question is how can reduces those duplicated code ? do I use Custom Attributes, or AuthorizeAttribute
here are some of the example code:
[Route("[controller]")]
[ApiController]
public class ExampleController : ControllerBase
{
public async Task<Result> Call_1(Rquest request)
{
string signaturel;
signature = Util.getHeaderSignature(request);
if(unit.IsSinatureValid(signaturel, someVar1, someVar2))
{
(My logic)
}
else{ return "InvalidSinaturemessage" }
}
public async Task<Result> Call_2(Rquest request)
{
string signaturel;
signature = Util.getHeaderSignature(request);
if(unit.IsSinatureValid(signaturel, someVar1, someVar2))
{
(My logic)
}
else{ return "InvalidSinaturemessage" }
}
}
above code is just for showing, the actual Sinature checking logic is around 20 lines of code on every single controller method.
Yes, you can do that using action filters. It's described in documentation
Put your code for checking into OnActionExecuting method. So, you can write Result in the action filter if the signature isn't valid.
In case you need specific result structure you can create your own ObjectResult:
public class ForbiddenObjectResult : ObjectResult
{
public string Message { get; private set; }
public ForbiddenObjectResult(object value, string message)
: base(value)
{
StatusCode = StatusCodes.Status403Forbidden;
Message = message;
}
}
...
string signaturel;
signature = Util.getHeaderSignature(context.HttpContext.Request);
if(!unit.IsSinatureValid(signaturel, someVar1, someVar2))
{
context.Result = new ForbiddenObjectResult(filterContext.ModelState, "InvalidSinaturemessage");
}
And to register it for all your endpoints(if needed):
services.AddControllersWithViews(options =>
{
options.Filters.Add<YourActionFilter>();
});
You can use token based authentication or filter method. For reference
Token based authentication
Custom Filter
I have a WebAPI project that is included as a dependency to a frontend React+Redux project.
I created a code first database with a controller exposing some HttpGet functions where the functions modify a DbContext database.
When the app runs the Startup.ConfigureServices is not called and the webapi calls from React gets me this error:
"SyntaxError: Unexpected token < in JSON at position 0"
WebAPI functions from a different controller which don't use DBcontext stuff work though..
In LinkController I have the following:
[Route("api/[controller]")]
public class LinkController : Controller
{
[HttpGet("[action]")]
public URLSubmitResult SubmitLink(string url)
{
return new URLSubmitResult() { Success = true };
}
public class URLSubmitResult
{
public bool Success { get; set; }
}
}
private readonly LinkDbContext _context;
public LinkController(LinkDbContext context)
{
_context = context;
}
In ValuesController the following:
[Route("api/[controller]")]
public class ValuesController : Controller
{
[HttpGet("[action]")]
public URLSubmitResult SubmitLink(string url)
{
return new URLSubmitResult() { Success = true };
}
public class URLSubmitResult
{
public bool Success { get; set; }
}
public ValuesController()
{
}
The call is not happening if I go api/link/submitlink?url=lol.com but it does if it's api/values/submitlink?url=lol.com
The actual issue, regardless of the error I'm receiving in the response, is that api/link/submit link is not resulting in the controller function being called.
Most likely scenario is an undefined result is coming back which isn't typecasting to json.
So basically the issue was that the referenced WebAPI assembly was not adding the appropriate service to the Dependency injection.
In Startup.ConfigureServices for the main project I added:
services.AddDbContext<LinkDbContext>(options =>
options.UseSqlServer(Configuration.GetConnectionString("LinkDBConnectionString")));
Along with the connection string (LinkDBConnectionString) the dependency injection worked and the WebAPI initialized.
From some more experience I've found this project setup is not really what I want which was to decouple the frontend from the WebAPI, including the WebAPI as a dependency to the frontend is not the answer in the case of web.
Instead I now run the WebAPI as its own thing in Visual Studio and do calls to it through the frontend.
In previous asp.net web api, I implement DefaultHttpControllerSelector to specify how I want the request to locate my controller. I often have different controllers with different names but intended for same processes. The only difference is that one is of higher version than the other.
For example, I could have a controller named BookingV1Controller, which would be meant to handle the version one of the service. I would also have BookingV2Controller, which was designed to handle the version two of the service. A client application would then make a request to the service with this url http://myservice.com/api/v2/booking/someaction?id=12. To handle the request, I would provide a custom implementation of DefaultHttpControllerSelector to select the appropriate version of the controller required based on the requested version.
However, I seems not to have a way to do this in ASP.NET Core. I have searched everywhere to no avail. No documentation that could help either.
I would appreciate if anyone can be of help to me here. Thanks.
UPDATE
I would also like to know what to do if the version is specified in a custom header. E.g X-Version:v1
UPDATE 2
The requirement was that the version of the service should not be exposed in the URL. If no version is present, the service returns with instruction on how to add the version. If a requested controller is not present in the version requested, the system searches through the lower versions. If it finds it in any lower versions, it uses that. The reason for this is to prevent repetition of controllers on all versions. But with ASP.NET Core, this might not be possible.
This is a very old question that I stumbled upon, but there are much better solutions now. There is this package
Microsoft.AspNetCore.Mvc.Versioning
Which has a much more feature rich way of implementing versioning controls. These include being able to use URL query strings, url paths, headers, or custom version readers. Being able to read the version from HTTPContext etc.
In short, you add the following into your ConfigureServices method in startup.cs
services.AddApiVersioning(o => {
o.ReportApiVersions = true;
o.AssumeDefaultVersionWhenUnspecified = true;
o.DefaultApiVersion = new ApiVersion(1, 0);
});
Then you have to decorate your controllers with an ApiVersion.
[ApiVersion("1.0")]
[Route("api/home")]
public class HomeV1Controller : Controller
{
[HttpGet]
public string Get() => "Version 1";
}
[ApiVersion("2.0")]
[Route("api/home")]
public class HomeV2Controller : Controller
{
[HttpGet]
public string Get() => "Version 2";
}
You can also implement it in the path by putting it in the route.
[ApiVersion("1.0")]
[Route("api/{version:apiVersion}/home")]
public class HomeV1Controller : Controller
{
[HttpGet]
public string Get() => "Version 1";
}
[ApiVersion("2.0")]
[Route("api/{version:apiVersion}/home")]
public class HomeV2Controller : Controller
{
[HttpGet]
public string Get() => "Version 2";
}
When you go down this method of actually having it implemented via the Microsoft package, it also means that you are able to deprecate versions, have version discovery, access the version number from the HttpContext easily etc. None of which you could really do if it's just hardcoded in your route.
For more info (Including using it in a header) :
http://dotnetcoretutorials.com/2017/01/17/api-versioning-asp-net-core/
http://www.hanselman.com/blog/ASPNETCoreRESTfulWebAPIVersioningMadeEasy.aspx
https://github.com/Microsoft/aspnet-api-versioning/wiki
I created a package for this purpose exactly after banging my head on this problem for a few days. It doesn't require attributes.
https://github.com/GoAheadTours/NamespaceVersioning
In summary, you can register an IApplicationModelConvention in your startup file that can iterate through controllers and register routes based on the namespaces. I created a v1 folder, and put my controller inside
The class that implements IApplicationModelConvention implements an Apply method with an ApplicationModel parameter that will have access to the Controllers in your app and their existing routes. If I see a controller does not have a route set up in my class I get the version from the namespace and use a pre-defined URL prefix to generate a route for that version.
public void Apply(ApplicationModel application) {
foreach (var controller in application.Controllers) {
var hasRouteAttribute = controller.Selectors.Any(x => x.AttributeRouteModel != null);
if (hasRouteAttribute) {
continue;
}
var nameSpace = controller.ControllerType.Namespace.Split('.');
var version = nameSpace.FirstOrDefault(x => Regex.IsMatch(x, #"[v][\d*]"));
if (string.IsNullOrEmpty(version)) {
continue;
}
controller.Selectors[0].AttributeRouteModel = new AttributeRouteModel() {
Template = string.Format(urlTemplate, apiPrefix, version, controller.ControllerName)
};
}
}
I have all the code up on github and a link to the package on nuget as well
Use the routing attributes to control versions.
i.e.
[Route("api/v1/[controller]")]
public class BookingV1Controller : Controller
{
....
}
[Route("api/v2/[controller]")]
public class BookingV2Controller : Controller
{
....
}
For more information relating to migrating from standard Web Api and .NET Core ASP.NET have a look at: MSDN: Migrating from ASP.NET Web Api
For that Add service API versioning to your ASP.NET Core applications
public void ConfigureServices( IServiceCollection services )
{
services.AddMvc();
services.AddApiVersioning();
// remaining other stuff omitted for brevity
}
QUERYSTRING PARAMETER VERSIONING
[ApiVersion( "2.0" )]
[Route( "api/helloworld" )]
public class HelloWorld2Controller : Controller {
[HttpGet]
public string Get() => "Hello world!";
}
So this means to get 2.0 over 1.0 in another Controller with the same route, you'd go here:
/api/helloworld?api-version=2.0
we can have the same controller name with different namespaces
URL PATH SEGMENT VERSIONING
[ApiVersion( "1.0" )]
[Route( "api/v{version:apiVersion}/[controller]" )]
public class HelloWorldController : Controller {
public string Get() => "Hello world!";
}
[ApiVersion( "2.0" )]
[ApiVersion( "3.0" )]
[Route( "api/v{version:apiVersion}/helloworld" )]
public class HelloWorld2Controller : Controller {
[HttpGet]
public string Get() => "Hello world v2!";
[HttpGet, MapToApiVersion( "3.0" )]
public string GetV3() => "Hello world v3!";
}
Header Versioning
public void ConfigureServices( IServiceCollection services )
{
services.AddMvc();
services.AddApiVersioning(o => o.ApiVersionReader = new HeaderApiVersionReader("api-version"));
}
When you do HeaderApiVersioning you won't be able to just do a GET in your browser, so I'll use Postman to add the header (or I could use Curl, or WGet, or PowerShell, or a Unit Test):
Image
please refer https://www.hanselman.com/blog/ASPNETCoreRESTfulWebAPIVersioningMadeEasy.aspx
I am trying to determine the correct method to inject a dependency into a controller where the concrete type to be injected is a variable based on a route data parameter.
So far I have the following set up which works perfectly for normal requests:
Controller
public class OrdersController : ODataController
{
private IOrderService ErpService { get; }
public OrdersController(IOrderService orderService)
{
ErpService = orderService;
}
[EnableQuery(PageSize = 100)]
public IQueryable<OrderDto> Get(ODataQueryOptions<OrderDto> queryOptions)
{
return ErpService.Orders(queryOptions);
}
...
// Post
// Patch/Put
// Delete
}
With the following OData route config, I can specify the route template should include a 'company' parameter:
Config
config.MapODataServiceRoute( "ODataRoute", "data/{company}", model, new DefaultODataPathHandler(),
conventions, new DefaultODataBatchHandler(GlobalConfiguration.DefaultServer));
This allows me to have a static method to read the company ID from the URL:
public static string GetSalesCompanyFromRequest()
{
var salesCompany = "";
if (HttpContext.Current == null) return "";
var routeData = HttpContext.Current.Request.RequestContext.RouteData;
if (routeData.Values.ContainsKey("company"))
{
salesCompany = routeData.Values["company"].ToString();
}
return salesCompany;
}
Then, using Ninject, I can chose which concrete instance of IOrderService to use (simplified for brevity):
kernel.Bind<IOrderService>()
.To<SageOrderService>()
.When(ctx => GetSalesCompanyFromRequest() == "101").InRequestScope();
kernel.Bind<IOrderService>()
.To<DynamicsAxOrderService>()
.When(ctx => GetSalesCompanyFromRequest() == "222").InRequestScope();
kernel.Bind<IOrderService>()
.To<SapOrderService>()
.When(ctx => GetSalesCompanyFromRequest() == "333").InRequestScope();
Connector Config
Id ErpType ConnectionString
--------------------------------------------
111 Sage "connectionstring1"
222 DynamicsAx "connectionstring2"
333 SAP "connectionstring3"
So here's how the following URLs get processed:
http://odata-demo/data/101/Orders
Creates and injects a SageOrderService into OrdersController
http://odata-demo/data/222/Orders
Creates and injects a DynamicsAxOrderService into OrdersController
The same logic applies to many different services, like:
SageStockService/AxStockService
SageBomService/AxBomService
etc
Note:
I chose to put the company Id in the URL so I could configure a reverse proxy to forward requests to a local web server closer to the target database.
This all works perfectly until I try to use OData Batching.
It seems then there is no HttpContext.Current (it is null) when I send a batched request.
This question asks something similar but does not account for OData batched requests.
Comments in this answer suggest injection by route data is code smell but does not elaborate.
So, the question is, how to I get HttpContext.Current for batched OData requests? Or Is there a better way to do what I'm trying to do?
Since the company is already in the route data, I could add an additional company parameter to every single action as follows to allow the company number to be passed in, then use a factory to get the right concrete type:
public class OrdersController : ODataController
{
[EnableQuery(PageSize = 100)]
public IQueryable<OrderDto> Get(ODataQueryOptions<OrderDto> queryOptions, string company)
{
var erpService = ErpServiceFactory.GetService(company);
return erpService.Orders(queryOptions);
}
...
// Post
// Patch/Put
// Delete
}
This means that I would also have to initialise the OrderService within each action which smells a bit.
I suppose this could be less smelly if I used an ActionFilter to locate and pass in the correct concrete type to the action:
public class RequiresOrderServiceAttribute : ActionFilterAttribute
{
public override void OnActionExecuting(HttpActionContext actionContext)
{
string salesCompany = "";
var data = actionContext.Request.GetRouteData();
if (data.Values.ContainsKey("company"))
{
salesCompany = data.Values["company"].ToString();
var orderService = ErpServiceFactory.GetService(company);
actionContext.ActionArguments.Add("erpService", orderService);
}
}
}
public class OrdersController : ODataController
{
[EnableQuery(PageSize = 100)]
public IQueryable<OrderDto> Get(ODataQueryOptions<OrderDto> queryOptions, IOrderService erpService)
{
return erpService.Orders(queryOptions);
}
...
// Post
// Patch/Put
// Delete
}
Thoughts?
I have started a new Web API project that requires that we switch the database the application is running to based on HTTP header information sent to the API. The application user will be identified by a HTTP header and the application should then change to use their database.
I have a base controller CrudControllerBase<T> ( to handle simple generic HTTP requests ) which creates a DataService<T> in it's constructor. All of my controllers will derive from this base controller and will have access to this DataService. The DataService is used to do common DB queries ( FindById(), FindAll(), etc. ) and more complex queries are bolted on using extension methods.
public abstract class CrudControllerBase<T> : ApiController where T : class, IEntity
{
protected IDataService<T> _dataService;
public CrudControllerBase()
{
this._dataService = new DataService<T>();
}
[HttpGet]
public virtual async Task<IHttpActionResult> Get(Guid id)
{
var model = await _dataService.FindByIdAsync(id);
return Ok<T>(model);
}
//code left out
}
public class OrdersController : CrudControllerBase<OrderItem>
{
}
and in the DataService I new up the DbContext class:
public class DataService<T> : IDataService<T> where T:class, IEntity
{
private readonly AppDbContext _context;
public DataService()
{
_context = new AppDbContext(); // need to pass in connection string
}
// code left out
}
I need to be able to pass in the connection string to the constructor of AppDbContext but in the constructor of CrudControllerBase I do not have access to the HttpRequestMessage to be able to pass this info to the DataService.
Can anyone suggest a solution ? I am quite happy to try a completely different way of doing this if someone can suggest something. Thanks !
OK so I have got this working. It may not be the best solution but it works and if anyone has any feedback / improvements then please share. Thanks to #AaronLS for pointing me in the right direction. This article also helped a lot.
The first step was to create a CustomControllerFactory that implements the IHttpControllerActivator interface. This gives you a Create method in which you can write your own code for newing up your Controllers. This is my CustomControllerFactory where I new up my controller passing in the HTTP Header as a string:
public class CustomControllerFactory : IHttpControllerActivator
{
public IHttpController Create(HttpRequestMessage request, HttpControllerDescriptor controllerDescriptor, Type controllerType)
{
var schemaKey = request.Headers.Where(k => k.Key == "schema").FirstOrDefault().Value.FirstOrDefault();
return (IHttpController)Activator.CreateInstance(controllerType, new string[] { schemaKey });
}
}
The next step is to tell the web API to use this method for instantiating controllers. To do this I added this line to my WebApiConfig class:
GlobalConfiguration.Configuration.Services.Replace(typeof(IHttpControllerActivator), new CustomControllerFactory());
The last thing I needed to was add a constructor to each controller which took in the string value and passed it to the base controller
public OrdersController(String databaseName) : base(databaseName) { }
and my base controller passes the parameter to the DataService
public CrudControllerBase(String databaseName)
{
this._dataService = new DataService<T>(databaseName);
}
and my database passes in the connection string to the AppDbContext() constructor
public DataService(String databaseName)
{
this._context = new AppDbContext(BuildConnectionString(databaseName));
}
I realise there is no error handling / security checking yet but I will add that in :-)