I have made a web API class for my Customer model. I have the standard methods (GET, POST, PUT, DELETE). The problem is, I want to implement another GET method which is a search. Something like this:
[HttpGet]
public IEnumerable<Customer> Search(string id)
{
var customers = customerRepository.Search(id);
return customers;
}
The search method performs a search based on the account number of my customers, using the .Contains() method.
The problem is, when I navigate to: mySite.com/api/Customers/Search/123 I get a 404. What am I doing wrong here?
While Darin's answers are always of top quality this question would actually benefit from an answer that explains how searching, paging and filtering should actually be done in any API and how it should be done using the most current version of Web API (v2).
This is a post which I consider a good resource on the matter (technology indenpendent):
http://www.vinaysahni.com/best-practices-for-a-pragmatic-restful-api
The answer should also reflect what's new in ASP.NET Web API v2 because Darin's answer is quite old.
Since this question comes up at the top when doing Google search for "asp.net web api searching" I will try to explain few things here.
To get as close as possible to REST principles with the latest version of ASP.NET Web API (v2) one should take a serious look at attribute routing that was introduced in the latest version. It is very hard to achieve RESTful routing with the old, classic, convention based routing (in global.asax.cs or RouteConfig.cs).
You should read more about that here
http://www.asp.net/web-api/overview/web-api-routing-and-actions/attribute-routing-in-web-api-2
Now, to go into details how to implement the specifics you ask about.
The most common practice is to expose these types of functionality through query string parameters.
Per REST principles, you should have one endpoint for your Customers resource, for instance
/api/customers
To achieve this you would decorate your GetCustomers() action in your Web API controller like this
[HttpGet]
[Route("/api/customers")]
public HttpResponseMessage GetCustomers(string q="", string sortBy="", string sortDirection="", bool active=true, ...)
{
// q = being optional search query
// sortBy = optional sort by column/property
// sortDirection = optional sort direction
// active = filter on 'active' column/property
// ... other filters may be applicable
}
You would implement this action closely to what you did in classic MVC if you wanted to provide filtered Views.
I would only introduce new controllers and custom actions if really needed, for some custom edge cases.
with regards to a comment about SearchFilter strongly typed object, let's explain that this won't work out of the box because the default model binder will not bind to this class when using GET requests.
So I'd either take those properties out of SearchFilter class and put them on the action itself so they'd bind via query string binder or use the [FromBody] binder if you wanted to bind from the request body. As per http://www.asp.net/web-api/overview/formats-and-model-binding/parameter-binding-in-aspnet-web-api
HTH
As per the default route setup only the standard controller action names are allowed (the RESTful ones and the dispatching is done based on the HTTP verb). If you want to violate the RESTful conventions and use some custom action names then you will have to modify your route setup in order to include the action name in the url: api/{controller}/{action}/{id}. Now you can send a request to /api/Customers/Search/123 which will invoke the Search action on the Customers API controller.
Related
Objective
The objective is to better understand how to develop modular (plugins by 3rd parties) approach to developing APIs, on top of ASP.NET Core.
To make plugin development easy to pick up it should rely on Conventions where possible, and deliver high value.
OData's queryability and being an Standard remains a compelling improvement to REST, providing a lot of bang for effort.
Issues
OData may be powerful, but the availability of current documentation remains sub par, hence unsure of its limits/capabilities.
Hence questions regarding what options one has to untangling routing issues due to conflicting endpoints from controllers in 3rd party modules.
Specifically:
Q1: Can a single OData EDM model disambiguate between two Controllers with the same name in two different namespaces?
Q2: Can one register an ODataController with a different Route than the ODataController name (eg route Foo points to BarController, when the convention is it would look for a FooController) without breaking default functionality? (eg: $count stops working for me)
Q2b: Even if we take over parsing incoming Uris into its odata path components as well as the logic it uses to find the relevant Controller?
Q3: If it can't, would the more practical approach to loading Plugins be for each Plugin to register its own EDM models?
Q4: Will using multiple EDM models work even if the second plugin/edm model has Controllers that exposes Models that has properties that refer back to Models exposed by Controllers in the base (or another dependency Plugin) EDM model?
Eg: Base EDM exposes Persons and Addresses, and second plugin EDM exposes maybe Customers, which has properties referencing Person and Addresses types, provided by the base EDM?
(I'm guessing it will work, but not 100% sure...anybody see an issue with this?)
Q5: How can one add new EDM models dynamically, resetting the routes?
For example if one uploaded a nuget package that was a plugin, and it contained its own EDM model describing its Models and Controllers...all this happening way after after.Run() was invoked, how can one kick the system in the head to refind/relearn what are valid routes?
Q6: Why is $count only available on ODataControllers that are registered by Convention, but doesn't work on those registered by RouteAttribute?
Background
The latest(?) documentation I found in a blog post here:
https://devblogs.microsoft.com/odata/attribute-routing-in-asp-net-core-odata-8-0-rc/
But it's just a blog post, and a lot remains missing to understand all the pieces and how they fit together.
Process So Far
I'm listing my notes below to help others see better what I am trying, in case someone can see what I am obviously getting wrong.
And if it helps and you want working code, I've uploaded my investigation efforts (actually thrashingArounds):
https://github.com/skysgh/Spikes.AspNetCore.ODataRouting
Setup
The default scenario in OData is to register EDM models with Controller Routes equal to the prefix of the name of the Controller.
Eg:
static string ODataPrefixWithSlash = "api/odata/v{version}/"
class SomeModelController : ODataController {...}
//is registered in an EDM model using convention of matching prefix of controller name:
var builder = new ODataConventionModelBuilder();
//to build a whole model:
builder.EntitySet<SomeModel>("SomeModel"); //This will get found
builder.EntitySet<SomeModel>("Renamed"); //Without further work causes 404 as route string != controller prefix
var edmModelA = builder.Build();
//and the model later registered as the source of OData routing info:
var mvcBuilder = builder.Services
.AddControllers()
.AddOData(
opt => opt.Count().Filter().Expand().Select().OrderBy().SetMaxTop(5)
//Add Module/PluginA Routes:
.AddRouteComponents(AppAPIConstants.ODataPrefixWithSlash,edmModelA);
if one has enabled
builder.Services.AddSwaggerGen();
app.UseODataRouteDebug();
one can navigate to ~odata and see the controller's Endpoints listed as:
~api/odata/v{version}/SomeModel
~api/odata/v{version}/SomeModel/$count <- note, showing for now (will break later...why?!)
Controller Choice Process
An article exists that talks about how routing has been updated in OData v8 RC, relying on RouteAttribute
https://devblogs.microsoft.com/odata/attribute-routing-in-asp-net-core-odata-8-0-rc/
After a lot of messing around, appears one can use a different name but
only IF one starts the route with the same prefix used to register the EDM Model (eg: api/odata/v{version}/
[ODataAttributeRouting]
// Half Works
// because the route starts with the same base as what the model was registered under
// fools it(?) (where?) into accepting it as an Odata controller.
// a) listed in ~/$odata as an odata controller (under api/odata/v{version}
// b) acting as an Odata controller (returning odata wrapper in json)
// c) but no default queryability (doesn't accept or list /$count)
// as the route starts with same prefix
// as Convention used when registereing EDM model
// But $count doesn't work!
[Route(AppAPIConstants.ODataPrefixWithSlash + "Renamed5")]
public class ValuesA5Controller : ODataController{
[EnableQuery(PageSize = 100)]
[HttpGet("")]
[HttpGet("Get")]
public IActionResult Get()
{
return Ok(FakeDataBuilder.Get());
}
}
The above permits registering the ODataController in the EDM model as follows:
builder.EntitySet<SomeModel>("Renamed5");
But it only HALF works.
it is listed as an OData Controller in $odata. ok.
it is acting (mostly) as an ODataController in that it accepts most OData commands $select, $filter, etc.
But it is failing at offering $count for some reason:
~api/odata/v{version}/Renamed5 <- showing, same as it was doing for earlier SomeModelController
~api/odata/v{version}/Renamed5/$count <- not showing, even though endpoint decorated with [EnableQuery].
Options?
Option A: More Route information
I suspect that one could decorate the Get method with more routes to enable $count:
[EnableQuery(PageSize = 100)]
[HttpGet("")]
[HttpGet("Get")]
[HttpGet("$count") ??? <- really???
public IActionResult Get() {....}
Certainly wish to avoid adding more codes as workaround (eg: what was tried here: https://stackoverflow.com/a/73042175/19926885)
but even if either worked, its more code, more sources of errors, etc. to watch out for.
If at all possible I'd like the least code, the most convention, while not locking in controller names.
Option B: Controller Selection process
As I said earlier, I don't know how or where the odata framework is magically matching "SomeModel" to "SomeModelController".
In an article I came across (https://devblogs.microsoft.com/odata/attribute-routing-in-asp-net-core-odata-8-0-rc/)
I saw mention of AttributeRoutingConvention: IODataControllerActionConvention which maybe could be put to use, but the blog post
didn't show when/how it could be registered, or replaced so have not been able to progress in that direction yet.
Also, iterating through registered Services I don't see anything inheriting from IODataControllerActionConvention. What's going on?
What I do see is:
//not sure yet what these do
IODataQueryRequestParser
IODataTemplateTranslator : Microsoft.AspNetCore.OData.Routing.Template.DefaultODataTemplateTranslator
IODataPathTemplateParser : Microsoft.AspNetCore.OData.Routing.Parser.DefaultODataPathTemplateParser`
But I don't have documentation on how they work, what they are for, etc.
Where next?
First of all THANK YOU for taking the time to read this long question!
Second, if you have advice as how I not thinking right as to how to solve the problem, that would help.
Third, if you are able to provide answers to the questions...wow. It's been a while I've looked for answers to this!
Took me a while to recognise the following basic misunderstandings on my part.
OData routing not something else but is an instance of the underlying WebAPI Routing
When one registers an EDM and providing the route prefix ("api/odata/v{version}"), it's just registering "~/api/odata/v{version}/~" as an action/route filter.
If you don't provide a [RouteAttribute] the convention is that it will make it up the Route segment from the Controller name (eg: 'PersonController' means that 'Person' will be used.
If you do provide a [RouteAttribute] on the Controller, you are saying this is the full (not a suffix!) route -- even if you provided a prefix when registering the EDM model.... To underline that point: just because HttpGet(...) acts as a suffix to the Route, doesn't meant the Route does too.
Which means that if it doesn't start with [Route("api/odata/v{version}")] -- maybe is just [Route("Renamed6")] -- it **doesn't match the route prefix used to register the edm model, so won't be handled by the models handler...so all it's doing is acting like all WebAPI controllers are doing and registers another route (whatever you gave), but won't consider it worthy of being OData queryability enabled....
So your route has to be [Route("api/odata/v{version}/Renamed6")].
Other than that, basics are that you'll get it confused if you register two models one with
"api/odata/v{version}", and another with
"api/odata/v{version}/plugin".
The first handler/whatever (i don't know what's behind the scenes here) will probably try to capture requests to plugin and not find a controller called "PluginController". I'm just guessing.
Finally, I think I saw issues with leaving dynamics tokens at the end for some reason. I had to change from using api/odata/PluginA/v{version} to api/odata/v{version}/PluginA . Not clear why.
Admittedly I still need to know a LOT more about the routing mechanism, but for now, this gets me forward again.
PS: Also...use Constants for building route strings. Turns out that Typos can really waste a lot of time :-(
I need to implement role based authorization on a .NET 5 API but the thing is that we don't want to decorate all the controllers with attributes and a list of roles, because all that configuration will come from either a config file (JSON) or an external service (TBD), in a way that roles will be mapped to controllers and actions and we would want to have something that centralizes all this logic, in a similar way we did before with Authentication Filters and Attributes.
I've been reading that now the idea from MS is that everything is handled with policies and requirements, but I don't know how to fit all that into our desired schema. Most of all because I don't see (or can't see) how can I access the Controller and Action's descriptors to know where I'm standing when I perform the authorization process.
Is there any way to achieve this on this new model?
EDIT: I found a way to get controller and action descriptors in order to do part of what I intended. Based on some other questions and articles I read and some tinkering on my own, I got the following:
public class AuthorizationFilter : IAsyncAuthorizationFilter
{
public Task OnAuthorizationAsync(AuthorizationFilterContext context)
{
var descriptor = (ControllerActionDescriptor)context.ActionDescriptor; //<<-- this is the key casting :)
var ctrlName = descriptor.ControllerName;
var actionName = descriptor.ActionName;
var userPrincipal = context.HttpContext.User;
//DO STUFF AND DECIDE RESULT TYPE BASED ON USER CLAIMS AND CURRENT CONTROLLER AND ACTION
context.Result = new ForbidResult();
context.Result = new UnauthorizedResult();
return Task.CompletedTask;
}
}
Then I could add this filter the following way:
services.AddControllers(x => x.Filters.Add<AuthorizationFilter>());
This way I could achieve something similar as before with ASP.NET MVC 4/5, but from what I can read, the .NET Core team tried to go away from this path by implementing the IAuthorizationRequirement and AuthorizationHandler<T> mechanism to replace all that, so my doubt remains: is this the correct way to do it in the new .NET Core 3.x / .NET 5 architecture? Or is there some other way I'm overlooking on how to get and process the controller/action being executed and pass it along to an AuthorizationHandler?
What you are looking for is called externalized authorization also referred to as attribute-based access control. In this model:
authorization logic is decoupled from the application
authorization logic is expressed as policies that build on top of attributes
attributes are key-value pairs that describe the subject, the action, the resource, and the context of what's going on (A user wants to execute an action on an object at a given time and place)
authorization is decided based on those policies in a logical central point (logical because you could very well have multiple instances of that central point colocated with your app for performance reasons). That logical central point in abac is known as the Policy Decision Point (PDP)
authorization is enforced based on the response back from the PDP in the place where you want to enforce it. This could be at a method level or at an API level or even a UI level: you choose. The component in charge of enforcing the decision is called a Policy Enforcement Point (PEP).
There's one main standard out there called xacml and its developer-friendly notation called alfa that will let you implement attribute-based access control. It's worth noting this model and approach is applicable to any app (not .NET-specific at all).
I'm moving a Web Api 2 project to MVC 6, since Microsoft is merging the two APIs in ASP.NET 5. In my WebApi project I had a custom Attribute Filter class that would authenticate, authorize and prevent transaction replays using a combination of public key, private key and HMAC authentication (basically, doing this with some tweaks to fit into my project).
Now in MVC6, as far as I understand I must stop using anything in the Microsoft.Web.Http namespace and instead use Microsoft.AspNet.Mvc. So I have done that, but the Microsoft.AspNet.Mvc.Filters doesn't seem to have any equivalent of Web Api 2's IAuthenticationFilter.
This is a problem for me because my customer AuthenticationFilter implemented all of IAuthenticationFilter, with all the logic in there. More importantly, it was using the Context to temporarily store the public key of the account, so my controller could access it to load up the account in turn.
So my question is, what is the proper way to filter requests in MVC6, using an Authentication Filter-like class to intercept the requests and return the appropriate status codes? I can't find any article that goes specifically in these details (they all tend to cover MVC5).
I know it's an older question, but hopefully someone (maybe even yourself) might find value in the answer.
MVC6 does in fact have an alternative. You have an
public abstract class AuthorizationFilterAttribute :
Attribute, IAsyncAuthorizationFilter, IAuthorizationFilter, IOrderedFilter
which basically tells you, that you can create your custom class, derive it from this (namespace of all of these interfaces, btw, is Microsoft.AspNet.Mvc.Filters and that should be it. You can either decorate the action with it, or you can do this in Startup.cs, to apply to all actions:
public void ConfigureServices(IServiceCollection services)
{
// Add MVC services to the services container.
services.AddMvc(options =>
{
// add an instance of the filter, like we used to do it
options.Filters.Add(new MySpecialFilter());
});
services.AddTransient<LogFilter>();
}
If you want to use a bit more logic in the filter (e.g. my LogFilter above) which is instantiated through DI, you need to use either Service Filters or Type Filters.
You can now decorate the actions with [ServiceFilter(typeof(LogFilter))] or use o.Filters.Add(new ServiceFilterAttribute(typeof(LogFilter))); in the Startup.cs file. But keep in mind, to do this you need to register the type with the DI container, like I did above with the .AddTransient<>() call.
IAuthenticationFilter is no more and IAuthorizationFilter simply does not replace it in MVC 6
Reason: authentication is NOT EQUAL to authorization.
Therefore IMO the authentication filter should stay available!
I'm trying to build an asp.net mvc 4 application.
I want the application to encompass both a HTML site and a restful api, e.g.
www.mysite.com/MyDetails/
www.mysiste.com/api/users/{userid}/Details/
In the above example I would use 2 controller classes.
MyDetailsController which inherits from System.Web.Mvc.Controller
DetailsController which inherits from System.Web.Http.ApiController
I've also added a simple 'Users Route' to the WebApiConfig:
routeTemplate: "api/users/{userid}/{controller}/{id}
In my early testing it appears as though the following scenarios are invalid:
www.mysite.com/api/users/12345/MyDetails/
www.mysite.com/Details/
Both of those return a 404.
This is definitely a good thing but what I'm trying to find out is why doesn't it work?
Can I rely on it not working or is it just coincidence in my simple test?
I've read about people struggling to develop a single MVC app/project that encompasses both HTML and REST apis but the most common complaint seems to be you can't duplicate controller names and it still seems like you can't simply use a namespace to differentiate them.
In this example I've deliberately designed the class names to avoid any conflict so what other gotchas are waiting to trip me up?
Thanks,
Chris A
Check your routes file, should be Global.asax under RegisterRoutes. The MapRoute call should tell you everything you need to know for MVC routing. Keep in mind, the order of the routes is important: top routes take priority over the bottom. Web API uses the WebApiConfig class and MapHttpRoute call to configure routes.
Please ensure you have put a (MVC) route on top of the action you wish to hit of your controller, default action being index.
[System.Web.Mvc.Route("Help")]
public ActionResult Index()
{
ViewBag.DocumentationProvider = Configuration.Services.GetDocumentationProvider();
return View(Configuration.Services.GetApiExplorer().ApiDescriptions);
}
Code above will hit this action method (provided your controller is registered and derived either from apiController or Controller) in the following way:
http://localhost:54541/help inside your IISExpress.
To register please do the following:
In "global.asax.cs", you’ll need to add:
AreaRegistration.RegisterAllAreas();
I've built my first grails application. My URL mappings are what the default application provides:
static mappings = {
"/$controller/$action?/$id?"{
constraints {
// apply constraints here
}
}
"/"(view:"/index")
"500"(view:'/error')
}
Senario
I have a controller called ColorController with actions save and list. It simply does something like this:
def save () {
def colorInstance = new Color(params)
colorInstance.save(flush: true)
}
def list () {
[colorList: Color.list, colorTotal: Color.count()]
}
I would like to build a simple API for these actions.
The save action should accept parameters as JSON and provide a successful message if the records save.
The list action should provide the list as JSON
Questions
Should I make a separate URL mapping for api? (e.g. http://<domain>/<app>/rest/controller/action)
Should I be making a separate controller for my API's
I am using spring security plugin for authentication. But at some point I might want to authenticate the restful api as well. What are some solutions for that?
If I use the same controller, how can I modify these simple actions to do what I need.
Before even looking below for my opinion/answers I would suggest to visit this SO Question for the basic understanding of RESTful WS in Grails.
Opinions:
"The save action should accept parameters as JSON and provide a successful message if the records save" - Save is mapped to POST RESTful. Instead of binding a JSON body to params it is bound to the request. In order to access the JSON object you just need to use request.JSON in the action method.
request.JSON instanceof JSONObject
"The list action should provide the list as JSON" - list() action is mapped to a GET Request and you can render the map as JSON in the list() as below
//Controller list()
import grails.converter.JSON
def list () {
[colorList: Color.list, colorTotal: Color.count()] as JSON
}
Answers to Questions:-
Should I make a separate URL mapping for api?
Abiding by the basics of REST, the client should only access the resource (Color in this case) and should not bother about the underlying controller or action. The server side logic should be abstracted from the client. URL Mapping is what the client would use to as form of request. I would have something like this in my url mapping for Color Resource.
/color/$id?(resource: "color")
or
/color/$id?(controller: 'color'){
action = [GET: "list", POST: "save"]
}
Should I be making a separate controller for my API's? - Depends on the way the App is designed. You also can have the above controller as the API. For example, currently I am working on a grails app which used AngularJS in the front End which connects to the Grails APP RESTFully. In order to achieve I had a RestClientController which works as an API to Angular. The rationale behind having a REST api in the same app is that in future we can expose the underlying service to external clients other than the Angular client present in the app itself.
I am using spring security plugin for authentication. But at some point I might want to authenticate the restful api as well. What are some solutions for that? - You can use Spring Security here as well. In my case I am using the plugin and I secure the controller by using the plugin's annotated component #Secured. I have custom OAuth enabled as well for authorization which interacts to the company wide LDAP and AD Groups.
If I use the same controller, how can I modify these simple actions to do what I need. - I think you would have got the answer to this question by now (after going through the SO question I mentioned above). Here is my opinion, controller actions can route to appropriate service classes which does the business implementations based on the request parameters.
For example,
//Action
def show(){
if(params.id){
colorService.getColor()
} else {
colorService.searchColor()
}
}
In the above example, the url mapping would be /color/123 or /color. In the former case, it will get the color and in the later it will search the colors