With the standard MapRoute method a can pass a string collection representing the namespaces in which to search for my controller. This seems to have disappeared from MapHttpRoute. How does one define the default namespaces using the new API routing?
We had this problem with the Umbraco core so we created our own IHttpControllerSelector, the source code can be found here:
https://github.com/WebApiContrib/WebAPIContrib/blob/master/src/WebApiContrib/Selectors/NamespaceHttpControllerSelector.cs
You can also install nuget package WebAPIContrib which contains NamespaceHttpControllerSelector.
To register this you can do this on app startup:
GlobalConfiguration.Configuration.Services.Replace(typeof(IHttpControllerSelector),
new NamespaceHttpControllerSelector(GlobalConfiguration.Configuration));
The implementation is pretty straight forward and only deals with routes that have the "Namespaces" datatoken set which you have to manually set since the MapHttpRoute doesn't support this. Example:
var r = routes.MapHttpRoute(
name: "DefaultApi",
routeTemplate: "api/{controller}/{id}",
defaults: new { id = RouteParameter.Optional }
);
r.DataTokens["Namespaces"] = new string[] {"Foo"};
The implementation also only caches controllers found with duplicate names since the underlying default implementation removes duplicates from it's cache.
That feature does not exist currently.
Although the feature does not exist at this moment, you can however do this by implementing your own IHttpControllerSelector.
This blog article digs a bit into the details: ASP.NET Web API: Using Namespaces to Version Web APIs
You don't need to set default namespaces with Web API, it will search for controllers in all namespaces in the referenced assemblies (public types with name ending by 'Controller' which implement IHttpController).
Before the MapHttpRoute Factory call add
System.Web.Mvc.ControllerBuilder.Current.DefaultNamespaces.Add("Namespace.Full.Controllers");
Related
I am using:
.NET Framework 4.7.2
Visual Studio 2017
OData V4
Web API V 5.2.4 with Entity Framework V6.2 code first with existing MS SQL DB
My $expand and $select commands are generating an error. For example, when I use the select command as follows:
http://localhost:62681/data/Advances?$select=Description
I get the following error:
The query specified in the URI is not valid. Could not find a
property named 'Description' on type
'Microsoft.AspNet.OData.Query.Expressions.SelectSome_1OfAdvance'
$filter and $orderby do work
The odd thing is that this used to work a couple of weeks ago, but when I came back from vacation I could not get it to work. Any help is appreciated.
I've scoured the internet for any clue to my problem, but no luck.
I've upgraded to Microsoft.OData.Core version=7.5.0 and Microsoft.OData.Edm" version="7.5.0
My register method in WebApi.Config does have the line config.Select().Expand().Filter().OrderBy().Count().MaxTop(null);
public static void Register(HttpConfiguration config)
{
// Web API configuration and services
// Web API routes
config.MapHttpAttributeRoutes();
config.Routes.MapHttpRoute(
name: "DefaultApi",
routeTemplate: "api/{controller}/{id}",
defaults: new { id = RouteParameter.Optional }
);
ODataModelBuilder builder = new ODataConventionModelBuilder();
config.Select().Expand().Filter().OrderBy().Count().MaxTop(null);
builder.EntitySet<Advance>("Advances");
builder.EntitySet<Advance_Payments>("Advance_Payments");
config.MapODataServiceRoute(
routeName: "ODataRoute",
routePrefix: "data",
model: builder.GetEdmModel());
config.AddODataQueryFilter(new SecureAccess2Attribute());
}
What am I doing wrong?
I found the solution in another stack overflow posting
EntitySetController $expand and $select not working
Go down about 2/3 of the page and look for a posting that starts with:
I came across a similar issue in OData V4. In this case it turned out if you used an attribute on the Get method and registered another attribute in your config, it errors because you are calling the EnableQuery code twice:
For OData V4, Remove the attribute [EnableQuery] on Controllers/Methods in the controller class IF you have enabled it globally. It accepts it only at one place.
I have an ASP.NET Web API I wrote and have published. Now that its out there we are looking at doing some improvements, and these improvements involve changes to certain calls which means we need to version to keep existing clients working.
I have used attribute routing so far in my app. Methods are invoked by: Controller/Action via RoutePrefix and Route attributes.
When I do need to create a V2 of my classes, I only want to recreate the classes that have actually changed, and redirect other routes back to v1 classes because they haven't changed. (Otherwise I just end up with a lot of boilerplate code, or duplicate code).
What I want to do is have the following routes work for my v1 version of classes:
Controller/Action
For V2 I want any new classes to go to V2, and any classes that haven't changed I want to return the HttpControllerDescriptor from V1 class. The route would look like v2/Controller/Action but would be redirected to Controller/Action.
I've implemented a IHttpControllerSelector and return the appropriate HttpControllerDescriptors but its not making the call into the method. I believe its because the routing information doesn't match the action. (When I put in an IHttpActionSelector and trace the exception it says "multiple actions were found that match the request).
So, I'm guess I'm wondering: Is this even possible? Is this the best way to achieve what I'm trying to do?
Here is what I implemented for versioning support in asp.net web api. Important to note I did not use attribute routing but explicit routes in WebApiConfig.cs so if you want to follow this pattern you would need to switch back to explicit routes. Also I do not prefer version information in the actual route, I use a custom (ie. "version") parameter in Accept header. I also set the version per mime type as in the below example. If version number is not set by the client or if the requested version does not exist this will fall back to default controller.
Create a class and inherit from DefaultHttpControllerSelector so you can fallback to base class behavior when you wanted to.
Override SelectController method as such:
public override HttpControllerDescriptor SelectController(HttpRequestMessage request)
{
IDictionary controllers = GetControllerMapping();
IHttpRouteData routeData = request.GetRouteData();
string controllerName = (string)routeData.Values["controller"];
HttpControllerDescriptor controllerDescriptor;
if (string.IsNullOrWhiteSpace(controllerName))
{
return base.SelectController(request);
}
if (!controllers.TryGetValue(controllerName, out controllerDescriptor))
{
return null;
}
string version = GetVersionFromAcceptHeader(request);
if (string.Equals(version, "1"))
{
return controllerDescriptor;
}
string newName = string.Concat(controllerName, "V", version);
HttpControllerDescriptor versionedControllerDescriptor;
if (controllers.TryGetValue(newName, out versionedControllerDescriptor))
{
return versionedControllerDescriptor;
}
return controllerDescriptor;
}
Register this controller selector in your webapiconfig Register method:
config.Services.Replace(typeof(IHttpControllerSelector), new YourControllerSelector(config));
I have a web api with the following routes:
routes.MapHttpRoute(
name: "DefaultGet",
routeTemplate: "api/{controller}/{id}",
defaults: new { controller = "Home", id = RouteParameter.Optional }
);
routes.MapHttpRoute(
name: "DefaultApi",
routeTemplate: "api/{controller}/{action}/{id}",
defaults: new { controller = "Home", action = "Index", id = RouteParameter.Optional }
);
These are two Actions inside a Controller:
public class UserController {
public HttpResponseMessage Get(int id) {}
public HttpRespnseMesseage GetDetails(int id) {}
}
The first route allows me to access the Get()-Method by "/api/User/4711" The second route allows me to access the GetDetails()-Method by "/api/User/GetDetails/4711"
This works.
But in addition, the Get()-Method can also be called by "/api/User/Get/4711" (and is listed in the automaticly generated documentation, too)
How can I make sure, that I can access the Get-Method by "/api/User/4711", but not by "/api/User/Get/4711"?
Note: I want to keep the default routes and do not want a solution that can only be achieved by removing my default routes and using route attributes instead
You're doing something quite strange: you're mixing up deafault RESTful style routing, with action based routing.
With your current configuraton, your second route will match any ULR like /api/User/XXX/4711 where XXX is anything. No matter if XXX is Get or anything else.
The only thing that you can do to avoid the second route to accept anything is to use route constraints. But, to do so, you must have a fixed set of rules. For example, if you only want to exclude Get you can do that with a regex constraint. But if you don't know the rules, of course, you cannot implement it.
Recommendation
As the OP finally decided himself, if you're using a RESTful API routing style it's much better to use routing attributes, available since Wep API v2. (In fact there was a Nuget package to support it on previous versions).
I'm upgrading a custom solution where I can dynamically register and unregister Web Api controllers to use the new attribute routing mechanism. However, it seems to recent update to RTM break my solution.
My solution exposes a couple of Web Api controllers for administration purposes. These are registered using the new HttpConfigurationExtensions.MapHttpAttributeRoutes method call.
The solution also allows Web Api controllers to be hosted in third-party assemblies and registered dynamically. At this stage, calling HttpConfigurationExtensions.MapHttAttributeRoutes a second time once the third-party controller is loaded would raise an exception. Therefore, my solution uses reflection to inspect the RoutePrefix and Route attributes and register corresponding routes on the HttpConfiguration object.
Unfortunately, calling the Web Api results in the following error:
"No HTTP resource was found that matches the request URI".
Here is a simple controller that I want to use:
[RoutePrefix("api/ze")]
public sealed class ZeController : ApiController
{
[HttpGet]
[Route("one")]
public string GetOne()
{
return "One";
}
[HttpGet]
[Route("two")]
public string GetTwo()
{
return "Two";
}
[HttpPost]
[Route("one")]
public string SetOne(string value)
{
return String.Empty;
}
}
Here is the first solution I tried:
configuration.Routes.MapHttpRoute("ZeApi", "api/ze/{action}");
Here is the second solution I tried:
var type = typeof(ZeController);
var routeMembers = type.GetMethods().Where(m => m.IsPublic);
foreach (MethodInfo method in routeMembers)
{
var routeAttribute = method.GetCustomAttributes(false).OfType<RouteAttribute>().FirstOrDefault();
if (routeAttribute != null)
{
string controllerName = type.Name.Substring(0, type.Name.LastIndexOf("Controller"));
string routeTemplate = string.Join("/", "api/Ze", routeAttribute.Template);
configuration.Routes.MapHttpRoute(method.Name, routeTemplate);
}
}
I also have tried a third solution, whereby I create custom classes that implement IHttpRoute and trying to register them with the configuration to no avail.
Is it possible to use legacy-style route mapping based upon the information contained in the new routing attributes ?
Update
I have installed my controller in a Web Application in order to troubleshoot the routing selection process with the Web Api Route Debugger. Here is the result of the screenshot:
As you can see, the correct action seems to be selected, but I still get a 404 error.
Update2
After further analysis, and per Kiran Challa's comment below, it seems that the design of Web Api prevents mixing attribute routing and conventional routing, and that what I want to do is not possible using this approach.
I have created a custom attribute [RouteEx] that serves the same purpose of the Web Api [Route] attribute, and now my code works perfectly.
I guess, since this is not possible using the conventional attribute routing, none of the answers on this question could legitimately be consisered valid. So I'm not nominating an answer just yet.
You shouldn't be required to use reflection and inspect the attribute-routing based attributes yourself. Attribute routing uses existing Web API features to get list of controllers to scan through.
Question: Before the switch to attribute routing, how were you loading these assemblies having the
controllers?
If you were doing this by IAssembliesResolver service, then this solution should work even with attribute routing and you should not be needing to do anything extra.
Regarding your Update: are you calling MapHttpAttributeRoutes?
I am looking at using WebAPI to create a Restful API. I want to create a launch url to provide URLs to the other portions/entities of the API. The obvious place to do this is ~/api/ however I cannot seem to wire up a route that will not give a 403 result. Any ideas?
EDIT: Just to be a bit clearer. I know how the default routing works in WebAPI, I.E. if I create a CustomersController that inherits from ApiController I can get there using ~/api/customers. What I want is a step before that where I could go to ~/api/ and would get a result a bit like:
[
{ Title: 'Customers', Url: '~/api/customers' }
]
I want this as my understanding is that RESTful services are metadata (think that the term) driven (basically discoverable and provide links to other resources in result). So there should be a single source url that points to all other resources in the API.
All you need to do for this is to add a new route which handles ~/api specifically.
In your project you will notice in the Application_Start method in the Global.asax a call is made to WebApiConfig.Register(GlobalConfiguration.Configuration), the WebApiConfig class is included in your project in the App_Start folder (along with various other config classes). If you look at the implementation of the Register call you will see that's where the ~/api/controller route is actually setup i.e.
config.Routes.MapHttpRoute(
name: "DefaultApi",
routeTemplate: "api/{controller}/{id}",
defaults: new { id = RouteParameter.Optional }
);
In order to make ~/api discoverable all you need to do is introduce a new route before the default which handles any calls to ~/api e.g.
config.Routes.MapHttpRoute(
name: "DiscoverableApi",
routeTemplate: "api",
defaults: new { controller = "Discoverable", }
);
Then add an ApiController to handle that call e.g.
public class DiscoverableController : ApiController
{
// GET api/values
public IEnumerable<string> Get()
{
return new string[] { "/users", "/photos", "/history" };
}
}
If you don't want to hard-code your URLs you will most likely need to look at something like Reflection to enumerable all the available ApiController's and their reachable endpoints (i.e. actions).
Take a look at the Microsoft ASP.NET Web API Help Page package from NuGet. Out of the box you can create a list of all your API endpoints which are returned as an MVC View. However you could customise it to return Json instead. You can also configure it to hang off any route you want, i.e.
api.mysite.com/help
myapi.com/api
etc