In an mvc app, how do you fallback to default IHttpControllerSeelector if the custom one doesn't match a url path - asp.net-web-api-routing

I read this article to try and created a versioned webapi controller using the namespace:
http://blogs.msdn.com/b/webdev/archive/2013/03/08/using-namespaces-to-version-web-apis.aspx
I don't want to break existing routes, so it's pretty important for me to be backward compatible.
and it makes it sound trivial in the end-note to use the default one if the routes don't match, but it's not trivial to me. If you can help that would be awsome.
thanks

Assuming that you are using the Code referred to in the Article, you could do this in the SelectController method:
public HttpControllerDescriptor SelectController(HttpRequestMessage request)
{
IHttpRouteData routeData = request.GetRouteData();
if (routeData == null)
{
throw new HttpResponseException(HttpStatusCode.NotFound);
}
// Get the namespace and controller variables from the route data.
string namespaceName = GetRouteVariable<string>(routeData, NamespaceKey);
if (namespaceName == null)
{
DefaultHttpControllerSelector selector = new DefaultHttpControllerSelector(_configuration);
return selector.SelectController(request);
}
// ... remaining code from article
}
When the route does not contain a version parameter, create an instance of the DefaultControllerSelector and return the value from it's SelectController method.

Related

How do I correctly map versioned resources in Grails?

I have grails running a REST API and using version numbers in the URL like so: https://api.mycompany.com/v2/metadata. I need to change the parameters of one of the endpoints, so I'm bumping the version up to v3. Only one controller is affected, so I would like to delegate the remaining calls back to the controllers for v2 without having to copy/paste everything again. Here's the relevant section of my UrlMappings.groovy:
class UrlMappings {
static mappings = {
"/v3/widget"(controller: "v3widget")
"/v3/$otherResource" {
// does not work, but illustrates what I want to happen
uri = { "/v2/" + params.otherResource }
}
// rest of my file...
"/v2/metadata"(controller: 'metadata')
...
What's the correct way to do this? I'm using grails 2.2.5
I would use a variable in the uri path, and instead of your example you would have the following mappings:
class UrlMappings {
static mappings = {
"/$apiVersion/widget"(controller: "v3widget")
"/$apiVersion/otherResource"(controller: "otherResource")
// rest of my file...
"/$apiVersion/metadata"(controller: 'metadata')
...
And then you could check for the value in controller:
class OtherResourceController {
def index(){
if(params.apiVersion == 'v2') {
...
} else {
...
}
}
}
The example here is checking for the string value, but you could go a bit deeper and actually convert the string value into internal api version enum representation which might be easier to manage. You could also do that in filter.
This way you can increment the logic changes and api will have a nice fallback, will delegate to default version.
But it gets really curly when you have couple of api versions layered one on the other.
The solution I found that works relies on the fact that wildcard mappings can also accept other regular expressions:
class UrlMappings {
static mappings = {
// v3 specific
"/v3/widget"(controller: "v3widget")
// v3/v2 common
"/v[23]/$otherResource" {
// normal mappings go here
}
// v2 specific
"/v2/metadata"(controller: 'v2metadata')
...
This solution works well since I don't have to repeat any mappings, and it's clear what's different between v2 and v3 resources.

MVC5 : Attribute Routing Precedence Among Controllers

I am using the Attribute Routing from MVC5 in my controllers.
Question:
Is there a way to control attribute routing precedence among controllers?
Consider the following
[Route("home/{action=index}/{username?}")]
public class HomeController : Controller
{
[Route("home/index/{username?}", Order = 1)]
[Route("home/{username?}", Order = 2)]
[Route("{username?}", Order = 3)]
public ActionResult Index()
{
// ... bunch of stuff
}
}
Base on the code above, HomeController.Index() action method should be invoked using the following requests:
domain/
domain/{username}
domain/home/
domain/home/{username}
domain/home/index/
domain/home/index/{username}
Second Controller:
[Authorize(Roles = "Member")]
[Route("profile/{action=index}")]
public class ProfileController : Controller
{
[Route("profile")]
public ActionResult Index()
{
}
}
The ProfileController.Index() should be invoked using the following request.
domain/profile
domain/profile/index
The problem
From the examples, if I send domain/profile in the url, an ambiguity exception is thrown. It seems that there is an ambiguity between domain/{username} and domain/profile.
Now, if I used convention-based routing, this would have worked (first match wins). But can it be done in MVC5 Attribute Routing? because I found that a third party library supports precedence among controllers
https://github.com/mccalltd/AttributeRouting/wiki/Controlling-Route-Precedence
routes.MapAttributeRoutes(config =>
{
config.AddRoutesFromController<ProfileController>();
config.AddRoutesFromController<HomeController>();
});
No, it is not possible in ASP.Net MVC 5.2.3 to prioritise controller routes over each other. If multiple match, then the order of the actions is ignored and an exception is thrown.
I have verified this by downloading the source from https://aspnetwebstack.codeplex.com/SourceControl/latest and checking the function GetControllerTypeFromDirectRoute (below). None of the calls made out of this function do anything to prioritise the routes, they are just found and reported back. As you can see, GetControllerTypeFromDirectRoute just throws on a multiple match.
Not great at all, but hopefully this will save someone else some time.
I put a manually mapped route in to avoid this issue.
private static Type GetControllerTypeFromDirectRoute(RouteData routeData)
{
Contract.Assert(routeData != null);
var matchingRouteDatas = routeData.GetDirectRouteMatches();
List<Type> controllerTypes = new List<Type>();
foreach (var directRouteData in matchingRouteDatas)
{
if (directRouteData != null)
{
Type controllerType = directRouteData.GetTargetControllerType();
if (controllerType == null)
{
// We don't expect this to happen, but it could happen if some code messes with the
// route data tokens and removes the key we're looking for.
throw new InvalidOperationException(MvcResources.DirectRoute_MissingControllerType);
}
if (!controllerTypes.Contains(controllerType))
{
controllerTypes.Add(controllerType);
}
}
}
// We only want to handle the case where all matched direct routes refer to the same controller.
// Handling the multiple-controllers case would put attribute routing down a totally different
// path than traditional routing.
if (controllerTypes.Count == 0)
{
return null;
}
else if (controllerTypes.Count == 1)
{
return controllerTypes[0];
}
else
{
throw CreateDirectRouteAmbiguousControllerException(controllerTypes);
}
}

ASP.NET Web Api Routing Customization

I have WebApi controllers that end with the "Api" suffix in their names (For ex: StudentsApiController, InstructorsApiController). I do this to easily differentiate my MVC controllers from WebApi controllers. I want my WebApi routes to look similar to
http://localhost:50009/api/students/5 and not http://localhost:50009/api/studentsapi/5.
Currently to achieve this, I am setting up routes like
routes.MapHttpRoute(
name: "GetStudents",
routeTemplate: "api/students/{id}",
defaults: new { controller = "StudentsApi", id = RouteParameter.Optional });
routes.MapHttpRoute(
name: "GetInstructors",
routeTemplate: "api/instructors/{id}",
defaults: new { controller = "InstructorsApi", id = RouteParameter.Optional });
This is turning out to be very cumbersome as I have to add a route for each method in my controllers. I am hoping there should be an easy way to setup route templates that automatically adds the "api" suffix the controller name while processing routes.
Following #Youssef Moussaoui's direction I ended up writing the following code that solved the problem.
public class ApiControllerSelector : DefaultHttpControllerSelector
{
public ApiControllerSelector(HttpConfiguration configuration)
: base(configuration)
{
}
public override string GetControllerName(HttpRequestMessage request)
{
if (request == null)
throw new ArgumentNullException("request");
IHttpRouteData routeData = request.GetRouteData();
if (routeData == null)
return null;
// Look up controller in route data
object controllerName;
routeData.Values.TryGetValue("controller", out controllerName);
if (controllerName != null)
controllerName += "api";
return (string)controllerName;
}
}
And register it in Global.asax as
GlobalConfiguration.Configuration.Services.Replace(typeof(IHttpControllerSelector),
new ApiControllerSelector(GlobalConfiguration.Configuration));
Now that ASP.NET Web API 2 is out, there is a much less cumbersome way to do more complex routing like that you suggested, by using attribute routing.
At the top of your controller just add the following attribute:
[RoutePrefix("api/students")]
public class StudentsApiController : ApiController
{
...
}
And then before each API method:
[Route("{id}"]
public HttpResponseMessage Get(int id)
{
...
}
There is a bit of setup required, but the positives of doing routing this way are many. For one, you can put the routing with the controllers and methods that do the actual work, so you're never searching around wondering if you have the right route. Secondly and more importantly, it's much easier to do more complex routing, like having the controller name different from the route name (like you want) or having very complex patterns to match against.
I think the extensibility point you're looking for is the controller selector. You can create a class that derives from DefaultHttpControllerSelector and overrides the GetControllerName to strip out the "api" part. You can then register this controller selector on your service's configuration Services.
Following Youssef's comment on muruug's answer would look something like this
public class ApiControllerSelector : DefaultHttpControllerSelector
{
public ApiControllerSelector (HttpConfiguration configuration) : base(configuration) { }
public override string GetControllerName(HttpRequestMessage request)
{
return base.GetControllerName(request) + "api";
}
}

Can my MVC2 app specify route constraints on Query String parameters?

My MVC2 app uses a component that makes subsequent AJAX calls back to the same action, which causes all kinds of unnecessary data access and processing on the server. The component vendor suggests I re-route those subsequent requests to a different action. The subsequent requests differ in that they have a particular query string, and I want to know whether I can put constraints on the query string in my route table.
For example, the initial request comes in with a URL like http://localhost/document/display/1. This can be handled by the default route. I want to write a custom route to handle URLs like http://localhost/document/display/1?vendorParam1=blah1&script=blah.js and http://localhost/document/display/1?vendorParam2=blah2&script=blah.js by detecting "vendor" in the URL.
I tried the following, but it throws a System.ArgumentException: The route URL cannot start with a '/' or '~' character and it cannot contain a '?' character.:
routes.MapRoute(
null,
"Document/Display/{id}?{args}",
new { controller = "OtherController", action = "OtherAction" },
new RouteValueDictionary { { "args", "vendor" } });
Can I write a route that takes the query string into account? If not, do you have any other ideas?
Update: Put simply, can I write routing constraints such that http://localhost/document/display/1 is routed to the DocumentController.Display action but http://localhost/document/display/1?vendorParam1=blah1&script=blah.js is routed to the VendorController.Display action? Eventually, I would like any URL whose query string contains "vendor" to be routed to the VendorController.Display action.
I understand the first URL can be handled by the default route, but what about the second? Is it possible to do this at all? After lots of trial and error on my part, it looks like the answer is "No".
QueryString parameters can be used in constraints, although it's not supported by default. Here you can find an article describing how to implement this in ASP.NET MVC 2.
As it is in Dutch, here's the implementation. Add an 'IRouteConstraint' class:
public class QueryStringConstraint : IRouteConstraint
{
private readonly Regex _regex;
public QueryStringConstraint(string regex)
{
_regex = new Regex(regex, RegexOptions.IgnoreCase);
}
public bool Match (HttpContextBase httpContext, Route route, string parameterName, RouteValueDictionary values, RouteDirection routeDirection)
{
// check whether the paramname is in the QS collection
if(httpContext.Request.QueryString.AllKeys.Contains(parameterName))
{
// validate on the given regex
return _regex.Match(httpContext.Request.QueryString[parameterName]).Success;
}
// or return false
return false;
}
}
Now you can use this in your routes:
routes.MapRoute("object-contact",
"{aanbod}",
/* ... */,
new { pagina = new QueryStringConstraint("some|constraint") });
You don't need a route for this. It is already handled by the default model binder. Query string parameters will be automatically bound to action arguments:
public ActionResult Foo(string id, string script, string vendorname)
{
// the id parameter will be bound from the default route token
// script and vendorname parameters will be bound from the request string
...
}
UPDATE:
If you don't know the name of the query string parameters that will be passed you could loop through them:
foreach (string key in Request.QueryString.Keys)
{
string value = Request.QueryString[key];
}
This post is old, but couldn't you write a route before your default route
this would only catch routes with "vendor" in the args
routes.MapRoute(
null,
"Document/Display/{id}?{args}",
new { controller = "VendorController", action = "OtherAction" },
new {args=#".*(vendor).*"}//believe this is correct regex to catch "vendor" anywhere in the args
);
And This would catch the rest
routes.MapRoute(
null,
"Document/Display/{id}?{args}",
new { controller = "DisplayController", action = "OtherAction" }
);
Haven't tried this and I am a novice to MVC but I believe this should work?? From what I understand if the constraint doesn't match the route isn't used. So it would test the next route. Since your next route doesn't use any constraint on the args, it should, match the route.
I tried this out and it worked for me.

Return PartialView in MVC3 Area is not searching in area

I am working on an ASP.Net MVC 3 RC project. I have one area named Drivers. I have a LoadPartial() action in a controller in the Drivers area that returns a PartialView(string, object); When this is returned I get an error on my webpage that says "The partial view 'PublicAttendanceCode' was not found." It searched the following locations:
~/Views/AttendanceEvent/PublicAttendanceCode.aspx
~/Views/AttendanceEvent/PublicAttendanceCode.ascx
~/Views/Shared/PublicAttendanceCode.aspx
~/Views/Shared/PublicAttendanceCode.ascx
~/Views/AttendanceEvent/PublicAttendanceCode.cshtml
~/Views/AttendanceEvent/PublicAttendanceCode.vbhtml
~/Views/Shared/PublicAttendanceCode.cshtml
~/Views/Shared/PublicAttendanceCode.vbhtml
Why is it not searching in the Drivers Area?
I have the following pretty basic routes in Global.asax.cs:
public static void RegisterRoutes(RouteCollection routes)
{
routes.IgnoreRoute("{resource}.axd/{*pathInfo}");
routes.MapRoute(
"Default", // Route name
"{controller}/{action}/{id}", // URL with parameters
new { controller = "Home",
action = "Index",
id = UrlParameter.Optional // Parameter defaults
}
);
}
And in DriversAreaRegistration.cs
public override void RegisterArea(AreaRegistrationContext context)
{
context.MapRoute(
"Drivers_default",
"Drivers/{controller}/{action}/{id}",
new { action = "RequestLeave", id = UrlParameter.Optional }
);
}
What am I missing that will make it look in the drivers area for the partial?
How are you providing area name to PartialView() method? I think you should be passing it in new { area = "Drivers" } as routeValues parameter.
The way that the MVC view engines know the area that they should look in is based on the route that was used to process the request.
In the case of the controller action that you have, are you certain that the request was processed by the area's route definition, or is it possible that the request was processed by the more general route that you defined in global.asax?
There are only four overloads of the method PartialView and it seems like neither of them accept routeValues as a parameter.
I solved this problem like this:
return PartialView(
VirtualPathUtility.ToAbsolute("~/Areas/MyArea/Views/Shared/MyView.cshtml"));
It works, but looks ugly.
This works too:
return PartialView("~/Areas/Admin/Views/Shared/MyView.cshtml", model);