Custom ApiExplorer with Namespace based ApiControllers - asp.net-mvc

I'm trying to add API documentation at my backend system.
Default ApiExplorer and Help page worked absolutely great until the moment I introduced versions to my Api Controllers.
In order to add versions I created sub folders under the Controllers folder:
v1
v2
v3
and have version based Api Controllers there. In order to have my Api discoverable I have to rewrite DefaultHttpControllerSelector to take into account namespaces provided by any client and map them to right controllers:
http://backend.com/api/v1/controller/action
http://backend.com/api/v2/controller/action
This have broken my default ApiExplorer and the following property returns ZERO api descriptions
Configuration.Services.GetApiExplorer().ApiDescriptions
How can I customize existent ApiExplorer and help him to find my Api Controllers and not to rewrite whole ApiExplorer implementation. I really need just to show where to find my Api Controllers.
Please advise.

I will show you a way to do that. This code is just for learning. Here I not talking about design and best practices, so feel free to change anything you want.
Well, You must follow the next steps:
1) Create a custom ApiExplorer:
public class MyApiExplorer: ApiExplorer
{
private readonly string _version;
public MyApiExplorer(string version) : base(GlobalConfiguration.Configuration)
{
_version = version != null ? version.ToUpperInvariant() : "V1";
foreach(var apiDescription in ApiDescriptions)
{
apiDescription.RelativePath = apiDescription.RelativePath.Replace("{version}", _version);
}
}
public override bool ShouldExploreController(string controllerVariableValue, HttpControllerDescriptor controllerDescriptor,
IHttpRoute route)
{
return controllerDescriptor.ControllerType.FullName.Contains(_version);
}
}
a) In the constructor _version will be converted to upperCase (just in
case it will be passed as lowerCase) but if it is null then it will
take V1 as default. Then change relative path to show specific version
instead of {version}.
b) ShouldExploreController (in short words)
decide if specific controller is taken to show in documentation. In
this case we will only show controllers that its type full name contains
choosed version.
2) Go to HelpController class and change Index method like this:
public ActionResult Index(string version)
{
//...
Configuration.Services.Replace(typeof(IApiExplorer), new MyApiExplorer(version));
return View(Configuration.Services.GetApiExplorer().ApiDescriptions);
}
We are replacing current ApiExplorer by our own in order to be
returned when call to Configuration.Services.GetApiExplorer()
Now you can use this .../help?version=v1 or .../help?version=v2 or .../help?version=v3 and you will get specific api controller documentation.

Turned out that there is nothing to do with ApiExplorer. As instead you should modify your namespace based controller selector:
NamespaceHttpControllerSelector : DefaultHttpControllerSelector
{
//...
public override IDictionary<string, HttpControllerDescriptor> GetControllerMapping()
{
var mapping = base.GetControllerMapping();
mapping["User"] = new HttpControllerDescriptor
{
Configuration = _httpConfig,
ControllerName = "User",
ControllerType = typeof(UserController)
};
//...
return mapping;
}
//... }
That is. After that default ApiExplorer will find you controllers and fetch all the actions.

I faced a similar problem recently, and solved mine with this:
2 LOC:
public class VersionControllerSelector : IHttpControllerSelector
to
public class VersionControllerSelector : DefaultHttpControllerSelector
...and...
public VersionControllerSelector(HttpConfiguration config)
to
public VersionControllerSelector(HttpConfiguration config) : base(config)

Related

Smallrye open api interceptor

I am developing a rest application.
Some endpoints require a custom header parameter, not related to authorisation. I created a custom annotation using jax-rs NameBinding. Here is an usage example:
#GET
#RequiresBankHeader
public int get(
#HeaderParam("bank")
#Parameter(ref = "#/components/parameters/banks")
String bank) {
return someService.getSomeInformation();
}
There is a provider that intercepts this call and do some routine using the information in the header parameter.
The problem is that I have to repeat '#HeaderParam("bank") #Parameter(ref = "#/components/parameters/banks") String bank' everywhere, just so it appears in Swagger, even though the service classes do not need it. I was able to at least reuse the parameter definition with ref = "#/components/parameters/banks", and declaring it in the OpenAPI.yml file, that Quarkus merges with generated code very nicely.
But I also want to create and interceptor to dynamically add this do the OpenApi definition whenever RequiresBankHeader annotation is present.
Is there a way to do it?
I dont think you can use interceptors to modify the generated Openapi schema output.
If all methods on a given endpoint require some parameter, you can specify it on class level like so:
#Path("/someendpoint")
public class MyEndpoint {
#HeaderParam("bank")
#Parameter(name = "bank")
String bank;
#GET
public Response getAll() {return Response.ok().build()}
#GET
#Path("{id}")
public Response someMethod(#PathParam("id") String id) {return Response.ok().build();}
}
As mentioned by Roberto Cortez, the MP OpenAPI spec provides a programmatic way to contribute metadata to the openapi.yml file.
It is not possible to detect an annotation in the JAX-RS endpoint definition, but it was good enough to automate what I needed. Since all methods that had the RequiresBankHeader return the same Schema, I was able to hack it like this:
public class OpenApiConfigurator implements OASFilter {
#Override
public Operation filterOperation(Operation operation) {
operation.getResponses().getAPIResponses().values().stream().
map(APIResponse::getContent).
filter(Objects::nonNull).
map(Content::getMediaTypes).
flatMap(mediaTypes -> mediaTypes.values().stream()).
map(MediaType::getSchema).
filter(Objects::nonNull).
map(Schema::getRef).
filter(Objects::nonNull).
filter(ref -> ref.contains("the common response schema")).
findAny().
ifPresent(schema -> {
ParameterImpl parameter = new ParameterImpl();
parameter.setRef("#/components/parameters/banks");
operation.addParameter(parameter);
});
return operation;
}
OpenApiConfigurator should be configure in the application properties, using mp.openapi.filter=com.yourcompany.OpenApiConfigurator

Autofac Dependencies Per Area

I'm creating a new MVC4 site using Autoface that has a public consumer site as well as an admin area for managing the consumer facing site. The admin site will be located in a different area be using the same services as the consumer facing site, but will not having some of the custom branding features.
I've followed the advice given elsewhere of having a ViewDataFactory which provides a set of shared data for the view to use. My goal is to provide a different ViewDataFactory depending on what Area you are in.
So for example, here is the Service that implements IViewDataFactory
builder.RegisterType<SelfServiceViewDataFactory>().As<IViewDataFactory>();
This gives me one ViewFactory which is injected into all my controllers. However what I'm trying to acheive is something like this (not functional code):
builder.RegisterType<ViewDataFactory>().As<IViewDataFactory>().ForType(ControllerBase1);
builder.RegisterType<DifferentViewDataFactory>().As<IViewDataFactory>().ForType(ControllerBase2);
Where the controller type or the MVC area would determine which service is resolved.
EDIT
To clarify my post has two questions:
Is there a way in Autofac to say "only for classes of type X, a service of type Y will be provided by instance Z" ?
Is there a way to change the Autofac behavior based on the Area the component is being used in?
From everything I've been reading the answer to #1 seems to be "no" unless you have a parameter to use to check which component to supply. I know Ninject can supply a dependency based on namespace so other frameworks seems to handle this case. Seems the solution is to either supply a parameter or have two different services defined.
I haven't really seen much discussion of Autofac and MVC areas so I'm guessing #2 is also not possible without a custom solution. Thanks!
Using named services is probably your best option. So you'd do something like:
builder
.RegisterType<ViewDataFactory>()
.Named<IViewDataFactory>("Area1");
builder
.RegisterType<DifferentViewDataFactory>()
.As<IViewDataFactory>("Area2");
And then if you want to avoid having to then manually register your controllers. You could use this code that I just cobbled together and haven't tested:
Put this attribute somewhere globally accessible:
[AttributeUsage(AttributeTargets.Parameter, AllowMultiple = false)]
public class ServiceNamedAttribute : Attribute
{
private readonly string _key;
public ServiceNamedAttribute(string key)
{
_key = key;
}
public string Key { get { return _key; } }
}
Add this module to your Autofac config:
public class ServiceNamedModule : Module
{
protected override void AttachToComponentRegistration(
IComponentRegistry registry, IComponentRegistration registration)
{
registration.Preparing +=
(sender, args) =>
{
if (!(args.Component.Activator is ReflectionActivator))
return;
var namedParameter = new ResolvedParameter(
(p, c) => GetCustomAttribute<ServiceNamedAttribute>(p) != null,
(p, c) => c.ResolveNamed(GetCustomAttribute<ServiceNamedAttribute>(p).Name, p.ParameterType));
args.Parameters = args.Parameters.Union(new[] { namedParameter });
};
}
private static T GetCustomAttribute<T>(ParameterInfo parameter) where T : Attribute
{
return parameter.GetCustomAttributes(typeof(T), false).Cast<T>().SingleOrDefault();
}
}
And then you can still auto-register your controllers by decorating the constructor like so:
public class Controller1
{
public Controller1(ServiceNamed["Area1"] IViewDataFactory factory)
{ ... }
}

How to set up Ninject DI to create Hyprlinkr RouteLinker instances

I have an MVC4 Web API project and I making use of Mark Seemann's Hyprlinkr component to generate Uris to linked resources. (Customer -> Addresses for example).
I have already followed Mark's guide on Dependency injection with Web API (changing appropriately for Ninject) bit I can't quite work out what I should do to inject a IResourceLinker into my controllers.
Following Mark's guide my IHttpControllerActivator.Create create method looks like this:
IHttpController IHttpControllerActivator.Create(HttpRequestMessage request, HttpControllerDescriptor controllerDescriptor, Type controllerType)
{
var controller = (IHttpController) _kernel.GetService(controllerType);
request.RegisterForDispose(new Release(() => _kernel.Release(controller)));
return controller;
}
It is in this method that the Hyprlinkr readme suggests to create the RouteLinker. Unfortunately I'm not sure how to register this with Ninject.
I can't just bind like below, as this results in multiple bindings:
_kernel.Bind<IResourceLinker>()
.ToMethod(context => new RouteLinker(request))
.InRequestScope();
I've got rebind working like this:
_kernel.Rebind<IResourceLinker>()
.ToMethod(context => new RouteLinker(request))
.InRequestScope();
But I'm concerned that changing the ninject binding graph is potentially a bad thing to do on every request.
What is the best way to achieve this?
Update following the request from Paige Cook
I'm using rebind here:
IHttpController IHttpControllerActivator.Create(HttpRequestMessage request, HttpControllerDescriptor controllerDescriptor, Type controllerType)
{
_kernel.Rebind<IResourceLinker>()
.ToMethod(context => new RouteLinker(request))
.InRequestScope();
var controller = (IHttpController) _kernel.GetService(controllerType);
request.RegisterForDispose(new Release(() => _kernel.Release(controller)));
return controller;
}
IHttpControllerActivator.Create is called on every request. The rest of the bindings are made in the standard way, by standard I mean in the class generated by using the Ninject.MVC3 nuget package.
My controller looks like this:
public class CustomerController : ApiController
{
private readonly ICustomerService _customerService;
private readonly IResourceLinker _linker;
public CustomerController(ICustomerService customerService, IResourceLinker linker)
{
_customerService = customerService;
_linker = linker;
}
public CustomerModel GetCustomer(string id)
{
Customer customer = _customerService.GetCustomer(id);
if (customer == null)
{
throw new HttpResponseException(Request.CreateResponse(HttpStatusCode.NotFound));
}
return
new CustomerModel
{
UserName = customer.UserName,
Firstname = customer.Firstname,
DefaultAddress = _linker.GetUri<AddressController>(c => c.Get(customer.DefaultAddressId)),
};
}
}
Register a delegate Function to give you the linker
_kernel.Bind<Func<HttpRequestMessage, IResourceLinker>>()
.ToMethod(context => (request) => new RouteLinker(request));
Inject the delegate
readonly Func<HttpRequestMessage, IResourceLinker> _getResourceLinker;
public controller(Func<HttpRequestMessage, IResourceLinker> getResourceLinker) {
_getResourceLinker = getResourceLinker;
}
Use in your actions
public async Task<Thingy> Get() {
var linker = _getResourceLinker(Request);
linker.GetUri( ... )
}
If you only need to use RouteLinker from ApiController derivates, you don't really need to go through all the DI hoops.
You can just create it within the Controller like this:
var linker = new RouteLinker(this.Request);
IMO, using DI with RouteLinker first becomes valuable when you need a RouteLinker further down the stack - but then again, I also only use RouteLinker as a Concrete Dependency...
Thanks for adding the code sample. Based on what you have posted, you are running into your Bind/Rebind issue because you are issuing the _kernel.Bind<IResourceLinker> in the IHttpControllerActivtor.Create method every time.
You need to move the _kernel.Bind<IResourceLinker> to be registered the same way your are registering the rest of your bindings in the
...standard way, by standard I mean in the class generated by using the Ninject.MVC3 nuget package.
There should not be any need for the IResourceLinker to be binded multiple times, and this is why you are getting multiple instances, because the binding is firing every time a controller is created by the IHttpControllerActivator.
Update:
Sorry that I missed the need for an HttpRequestMessage as a constructor argument, I would go with Anthony Johnson's answer on this one.

Best way of having Service communicate errors to Controller

When the Service layer is only executing a task (checking if Id exists, sending an email, etc.), what is the best way for it to let the controller know if there were any errors?
Two solutions I can think of:
Always passing in an extra "broken rules" parameter by reference to the methods in the Service layer which it would update if there were any error.
Have the Service raise an exception and having the controller do a try/catch.
Are either one of these two approaches recommended? If not, what approach could I take to have the Service layer let the controller know what something went wrong (such as invalid parameter)?
Your service should collection all the broken rules and after that throw the "BrokenRuleException". Your controller will catch the "BrokenRuleException" and then use the brokenrules to update the user interface.
I created interface:
public interface IModelStateWrapper
{
void AddModelError(string name, string error);
}
Then I created implementation for every controller:
public class ControllerModelStateWrapper : IModelStateWrapper
{
private ModelStateDictionary _dictionary;
public ControllerModelStateWrapper(ModelStateDictionary dictionary)
{
_dictionary = dictionary;
}
public void AddModelError(string name, string error)
{
if (_dictionary[name] == null)
_dictionary.Add(name, new ModelState());
_dictionary[name].Errors.Add(error);
}
}
Every service implements:
public interface IModelWrapperService
{
IModelStateWrapper ModelWrapper {get;set;}
}
And then I set it in Controller:
public UserController(IUserService service)
{
_service.ModelWrapper = new ControllerModelStateWrapper(ModelState);
}
IModelStateWrapper is not the best name, because this interface can work not only with Controller.ModelState. Works pretty ok. You can easily replace IModelStateWrapper with mock or other implementation in your service tests. This solution also automatically sets ModelState as invalid.
I think that throwing the BrokenRuleException is a good choice.
Personally, I don't like to put state in a service, it's often a singleton (performed by a DI container), and only has other singletons collaborators (in my case, domain objects).

ASP.NET MVC QueryString defaults overriding supplied values?

Using ASP.NET MVC Preview 5 (though this has also been tried with the Beta), it appears that querystring defaults in a route override the value that is passed in on the query string. A repro is to write a controller like this:
public class TestController : Controller
{
public ActionResult Foo(int x)
{
Trace.WriteLine(x);
Trace.WriteLine(this.HttpContext.Request.QueryString["x"]);
return new EmptyResult();
}
}
With route mapped as follows:
routes.MapRoute(
"test",
"Test/Foo",
new { controller = "Test", action = "Foo", x = 1 });
And then invoke it with this relative URI:
/Test/Foo?x=5
The trace output I see is:
1
5
So in other words the default value that was set up for the route is always passed into the method, irrespective of whether it was actually supplied on the querystring. Note that if the default for the querystring is removed, i.e. the route is mapped as follows:
routes.MapRoute(
"test",
"Test/Foo",
new { controller = "Test", action = "Foo" });
Then the controller behaves as expected and the value is passed in as the parameter value, giving the trace output:
5
5
This looks to me like a bug, but I would find it very surprising that a bug like this could still be in the beta release of the ASP.NET MVC framework, as querystrings with defaults aren't exactly an esoteric or edge-case feature, so it's almost certainly my fault. Any ideas what I'm doing wrong?
The best way to look at ASP.NET MVC with QueryStrings is to think of them as values that the route does not know about. As you found out, the QueryString is not part of the RouteData, therefore, you should keep what you are passing as a query string separate from the route values.
A way to work around them is to create default values yourself in the action if the values passed from the QueryString are null.
In your example, the route knows about x, therefore your url should really look like this:
/Test/Foo or /Test/Foo/5
and the route should look like this:
routes.MapRoute("test", "Test/Foo/{x}", new {controller = "Test", action = "Foo", x = 1});
To get the behavior you were looking for.
If you want to pass a QueryString value, say like a page number then you would do this:
/Test/Foo/5?page=1
And your action should change like this:
public ActionResult Foo(int x, int? page)
{
Trace.WriteLine(x);
Trace.WriteLine(page.HasValue ? page.Value : 1);
return new EmptyResult();
}
Now the test:
Url: /Test/Foo
Trace:
1
1
Url: /Test/Foo/5
Trace:
5
1
Url: /Test/Foo/5?page=2
Trace:
5
2
Url: /Test/Foo?page=2
Trace:
1
2
Hope this helps clarify some things.
One of my colleagues found a link which indicates that this is by design and it appears the author of that article raised an issue with the MVC team saying this was a change from earlier releases. The response from them was below (for "page" you can read "x" to have it relate to the question above):
This is by design. Routing does not
concern itself with query string
values; it concerns itself only with
values from RouteData. You should
instead remove the entry for "page"
from the Defaults dictionary, and in
either the action method itself or in
a filter set the default value for
"page" if it has not already been set.
We hope to in the future have an
easier way to mark a parameter as
explicitly coming from RouteData, the
query string, or a form. Until that is
implemented the above solution should
work. Please let us know if it
doesn't!
So it appears that this behaviour is 'correct', however it is so orthogonal to the principle of least astonishment that I still can't quite believe it.
Edit #1: Note that the post details a method of how to provide default values, however this no longer works as the ActionMethod property he uses to access the MethodInfo has been removed in the latest version of ASP.NET MVC. I'm currently working on an alternative and will post it when done.
Edit #2: I've updated the idea in the linked post to work with the Preview 5 release of ASP.NET MVC and I believe it should also work with the Beta release though I can't guarantee it as we haven't moved to that release yet. It's so simple that I've just posted it inline here.
First there's the default attribute (we can't use the existing .NET DefaultValueAttribute as it needs to inherit from CustomModelBinderAttribute):
[AttributeUsage(AttributeTargets.Parameter)]
public sealed class DefaultAttribute : CustomModelBinderAttribute
{
private readonly object value;
public DefaultAttribute(object value)
{
this.value = value;
}
public DefaultAttribute(string value, Type conversionType)
{
this.value = Convert.ChangeType(value, conversionType);
}
public override IModelBinder GetBinder()
{
return new DefaultValueModelBinder(this.value);
}
}
The the custom binder:
public sealed class DefaultValueModelBinder : IModelBinder
{
private readonly object value;
public DefaultValueModelBinder(object value)
{
this.value = value;
}
public ModelBinderResult BindModel(ModelBindingContext bindingContext)
{
var request = bindingContext.HttpContext.Request;
var queryValue = request .QueryString[bindingContext.ModelName];
return string.IsNullOrEmpty(queryValue)
? new ModelBinderResult(this.value)
: new DefaultModelBinder().BindModel(bindingContext);
}
}
And then you can simply apply it to the method parameters that come in on the querystring, e.g.
public ActionResult Foo([Default(1)] int x)
{
// implementation
}
Works like a charm!
I think the reason querystring parameters do not override the defaults is to stop people hacking the url.
Someone could use a url whose querystring included controller, action or other defaults you didn't want them to change.
I've dealt with this problem by doing what #Dale-Ragan suggested and dealing with it in the action method. Works for me.
I thought the point with Routing in MVC is to get rid of querystrings. Like this:
routes.MapRoute(
"test",
"Test/Foo/{x}",
new { controller = "Test", action = "Foo", x = 1 });

Resources