MVC5 : Attribute Routing Precedence Among Controllers - asp.net-mvc-routing

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);
}
}

Related

How to design multiple ways to invoke REST API

I am using ASP.NET Web API. I want to REST uri to be
GET /api/v1/documents/1234/download or
GET /api/v1/documents/1234?act=download or
GET /api/v1/documents?id=1234&act=download
Is it possible to have multiple ways to call REST API Url? Is it recommended?
I am using Attribute Routes only
[RoutePrefix("api/v1")]
public class DocumentController : ApiController
{
private readonly DomainService _domainService;
public DocumentController(DomainService domainService)
: base(domainService)
{
_domainService = domainService ?? throw new ArgumentNullException(nameof(domainService));
}
[HttpGet]
[Route("documents/{id:int}")]
public async Task<IHttpActionResult> DownloadDocument([FromUri]int id, [FromUri]string act)
{
if (string.IsNullOrEmpty(act) || act.ToUpper() != "DOWNLOAD")
{
return BadRequest("Invalid action parameter.");
}
return await service.DownloadFile(id);
}
}
with above code only GET /api/v1/documents/1234?act=download works. Is it possible to configure route in a such way that all 3 routes will invoke same action method?
You can add as many Route attributes as required to each method.
So you could do this to your method:
[Route("documents")] // matches /documents?id=123&act=download
[Route("documents/{id:int}")] // matches /documents/123?act=download
[Route("documents/{id:int}/{act}")] // matches /documents/123/download
Personally I think this is quite long-winded, and would try to stick to a single style (the last one if I could choose), but I guess it could depend on your requirements.

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";
}
}

How do I configure Fubu for a view without a controller?

I have an Index action on a controller that's not doing anything.
public EmptyModel Index()
{
return null;
}
The Index view simply displays some html, with jQuery-driven ajax and the MasterPage doing all the heavy lifting on this particular page. When I remove this action function from it's controller, the aspx view will no longer display.
More Information and Update:
After making the changes mentioned in Chad's answer the url that used to return the index view now instead returns a 404. This issue may exist because most of the views' folder structure is done in the early Fubu Framework style (with View_Page_Type_Declarations.cs and no code-behinds), rather than using the more intuitive and more recent default folder conventions. But it's possible my analysis is off.
Here's my FubuRegistry:
public WebAppFubuRegistry()
{
IncludeDiagnostics(true);
Services(x => x.SetServiceIfNone<IWebAppSecurityContext, WebAppSecurityContext>());
Applies.ToThisAssembly()
.ToAssemblyContainingType<HomeController>();
Actions
.IncludeClassesSuffixedWithController();
Routes
.UrlPolicy<WebAppUrlPolicy>()
.IgnoreControllerNamespaceEntirely()
.ConstrainToHttpMethod(action => action.Method.Name.StartsWith("Perform"), "POST");
Views
.TryToAttach(x=> x.by<ViewAndActionInDifferentFolders>())
.TryToAttachWithDefaultConventions()
.RegisterActionLessViews(WebFormViewFacility.IsWebFormView,
chain => chain.PartialOnly());
/*Behavior Code */
}
WebAppUrlPolicy:
public class WebAppUrlPolicy : IUrlPolicy
{
public bool Matches(ActionCall call, IConfigurationObserver log)
{
return true;
}
public IRouteDefinition Build(ActionCall call)
{
if(call.IsForHomeController())
return new RouteDefinition("home");
if(call.IsAnIndexCall())
return new RouteDefinition(call.ControllerPrefix());
var otherRoute = new RouteDefinition(call.ToControllerActionRoute());
return otherRoute;
}
}
ViewAndActionInDifferentFolders:
public class ViewAndActionInDifferentFolders : IViewsForActionFilter
{
public IEnumerable<IViewToken> Apply(ActionCall call, ViewBag views)
{
if (call.IsForHomeController())
{
var viewTokens = views.ViewsFor(call.OutputType()).Where(x => x.Name == "HomeIndexView");
return new[] { new WebAppViewToken(call, viewTokens, "home") };
}
if (call.IsJsonCall())
{
return new List<IViewToken>();
}
return CreateSingleTokenList(call, views);
}
private static IEnumerable<WebAppViewToken> CreateSingleTokenList(ActionCall call, ViewBag views)
{
return new[] { new WebAppViewToken(call, views.ViewsFor(call.OutputType())) };
}
}
How do I reconfigure Fubu so that I can use a view without the action?
What changes need to be made to remove the action function above, and still maintain the same functionality?
In your FubuRegistry, in the "Views" section, add:
.RegisterActionLessViews(WebFormViewFacility.IsWebFormView, chain => chain.PartialOnly());
For example, the whole views section may look like:
Views
.TryToAttachWithDefaultConventions()
.RegisterActionLessViews(
WebFormViewFacility.IsWebFormView,
chain => chain.PartialOnly());
Note that you can both ASPX and ASCX for headless views. If you only want ASCX files, then you can use WebFormViewFacility.IsWebFormControl instead.
Works for me:
Views.RegisterActionLessViews(type => type.Name == "StaticView",
chain => chain.Route = new RouteDefinition("StaticView"));

asp.net MVC 2 - most elegant way of isolating guard code - guarding against null controller parameters

I have a very simple problem, but I'm looking for the 'best' solution to the following:
I have multiple controller actions something like this:
public ActionResult DoSomething(PackageViewModel packageByName, DoSomethingInputModel inputModel)
{
if (packageByName == null)
{
Response.StatusCode = 404;
Response.StatusDescription = "Package not found : " + RouteData.GetRequiredString("packageName");
return View("Error");
}
...
What is the best way to isolate this cross cutting concern?
I can make a function
I can use an AOP tool like PostSharp
ActionFilter
Other?
In fact ActionFilter is an AOP. Write your own ActionFilter implementation to chceck if parameter is not null. If you always need to check the same thing on the beggining of your controller execution then it's the best way. It's easy to write, resusable in whole application and very MVC 2.
Here's what I implemented (based on #ƁukaszW.pl answer)
Hopefully this will save someone some time.
public class GuardAgainstNullPackage : ActionFilterAttribute
{
public override void OnActionExecuting(ActionExecutingContext filterContext)
{
BookingController controller = ((BookingController)filterContext.Controller);
if (filterContext.ActionParameters["packageByName"] == null || !(filterContext.ActionParameters["packageByName"] is PackageViewModel))
{
controller.Response.StatusCode = 404;
controller.Response.StatusDescription = "Package not found : " + filterContext.RouteData.GetRequiredString("packageName");
filterContext.Result = new ViewResult() { ViewName = "Error" };
}
base.OnActionExecuting(filterContext);
}
}

EFPocoAdapter -- PopulatePocoEntity has null PocoEntity

I'm trying EF with the EFPocoAdapter for the first time. I have a relatively simple TPH scenario with one table and two types, each inheriting from an abstract base class.
My model validates through EdmGen, and my PocoAdapter.cs and xxxEntities.cs files generate fine as well. (well, actually, there are some namespace problems that I'm currently tweaking by hand until we figure out where to go next.)
When I run a simple test to retrieve data:
using (CINFulfillmentEntities context = new CINFulfillmentEntities())
{
// use context
var alerts = from p in context.Notifications.OfType<Alert>()
select p;
foreach (var alert in alerts)
{
Assert.IsNotNull(alert);
}
}
I get an error in the PocoAdapter class, claiming that PocoEntity is null is the following method inside my base class's adapter:
public override void PopulatePocoEntity(bool enableProxies)
{
base.PopulatePocoEntity(enableProxies);
PocoEntity.Owner = _Owner.CreatePocoStructure();
if (!(PocoEntity is IEntityProxy))
{
}
}
Any ideas from anyone?
So, after a little more debugging, I think this is related to proxies. Inside PocoAdapterBase we have the following method:
protected PocoAdapterBase(TPocoClass pocoObject)
{
_context = ThreadLocalContext.Current;
bool allowProxies = false;
if (_context != null)
{
allowProxies = _context.EnableChangeTrackingUsingProxies;
}
_pocoEntity = pocoObject ?? (TPocoClass)(allowProxies ? CreatePocoEntityProxy() : CreatePocoEntity());
Init();
InitCollections(allowProxies);
RegisterAdapterInContext();
}
The line that sets _pocoEntity calls CreatePocoEntityProxy, which returns null.
More info as I find it.