1

I need to create a route constraint, but this constraint needs to use one of my services, and this service method uses async.

Now ideally, the data that is returned from this routeConstraint, i would like to pass to the controller for use within the action that is being called, if the constraint has been met.

Users will call a controller with an extra parameter, we will call myName. if this value appears in the database, i would like that record in the controller method.

Path to call controller looks like so, where data is the name of my controller, and myName is a string, that i need to check if it exists in the database.

http://localhost/api/data/myName

If myName doesnt exist, the controller should not be called. If it does, then the method should be called, but with the myName value available.

Not sure if maybe i need to use something else then a route constraint?

NOTE: I cannot add this as a parameter against each method in this controller, so please do not suggest it.

Gillardo
  • 9,518
  • 18
  • 73
  • 141

2 Answers2

1

You could implement your own IRouter, which is resolved asynchronously by MVC6. IRouter is the interface that every route implements, so you are operating at a lower level.

namespace Microsoft.AspNet.Routing
{
    public interface IRouter
    {
        // Populates route data (including route values) based on the
        // request
        Task RouteAsync(RouteContext context);

        // Derives a virtual path (URL) from a list of route values
        VirtualPathData GetVirtualPath(VirtualPathContext context);
    }
}

RouteAsync does the following:

  1. Analyzes the request to determine if it matches the route.
  2. If we have a match, set the route values.
  3. If we have a match, pass the call to the next IRouter (typically it is the MvcRouteHandler).

GetVirtualPath does the following:

  1. Compares a set of route values to see if they match the route.
  2. If it matches, converts the route values into a virtual path (URL). This should usually be the exact inverse of the logic in RouteAsync so we generate the same URL that is matched.
  3. The framework returns the virtual path to any calls to the UrlHelper, such as those made by ActionLink and RouteLink.

A typical implementation of RouteAsync looks something like this.

public async Task RouteAsync(RouteContext context)
{
    // Request path is the entire path (without the query string)
    // starting with a forward slash.
    // Example: /Home/About
    var requestPath = context.HttpContext.Request.Path.Value;

    if (!requestPath == <some comparison (slice the string up if you need to)>)
    {
        // Condition didn't match, returning here will
        // tell the framework this route doesn't match and
        // it will automatically call the next route.

        // This is similar to returning "false" from a route constraint.
        return;
    }

    // Invoke MVC controller/action.
    // We use a copy of the route data so we can revert back
    // to the original.
    var oldRouteData = context.RouteData;
    var newRouteData = new RouteData(oldRouteData);
    newRouteData.Routers.Add(_target);

    // TODO: Set this in the constructor or based on a data structure.
    newRouteData.Values["controller"] = "Custom"; 
    newRouteData.Values["action"] = "Details";

    // Set any other route values here (such as "id")

    try
    {
        context.RouteData = newRouteData;

        // Here we are calling the nested route asynchronously.
        // The nested route should generally be an instance of
        // MvcRouteHandler.

        // Calling it is similar to returning "true" from a
        // route constraint.            
        await _target.RouteAsync(context);
    }
    finally
    {
        // Restore the original values to prevent polluting the route data.
        if (!context.IsHandled)
        {
            context.RouteData = oldRouteData;
        }
    }
}

See the answers here and here for some other examples.

Community
  • 1
  • 1
NightOwl888
  • 55,572
  • 24
  • 139
  • 212
0

I suggest you use AsyncActionFilter for this purpose.

Suppose your route template: {controller}/{action}/{myName}

Implement AsyncActionFilter:

using Microsoft.AspNet.Mvc;
using Microsoft.AspNet.Mvc.Filters;
using System;
using System.Collections.Generic;
using System.Linq;
using System.Threading.Tasks;
using Microsoft.Framework.Internal;

namespace ActionFilterWebApp.Filters
{
    public class ThirdPartyServiceActionFilter : ActionFilterAttribute
    {
        public override Task OnActionExecutionAsync(ActionExecutingContext context, ActionExecutionDelegate next)
        {
            var routeKey = "myName";
            var routeDataValues = context.RouteData.Values;
            var allowActionInvoke = false;

            if (routeDataValues.ContainsKey(routeKey))
            {
                var routeValue = routeDataValues[routeKey];
                //allowActionInvoke = ThirdPartyService.Check(routeValue);
            }

            if (!allowActionInvoke)
            {
                //if you setting up Result property - action doesn't invoke
                context.Result = new BadRequestResult();
            }

            return base.OnActionExecutionAsync(context, next);
        }
    }
}

Add ThirdPartyServiceActionFilter on your controller:

   [ThirdPartyServiceActionFilter]
    public class HomeController : Controller
    {
        public IActionResult Index()
        {
            return View();
        }
    }
Stas Boyarincev
  • 3,690
  • 23
  • 23