开发者

How to achieve a dynamic controller and action method in ASP.NET MVC?

In Asp.net MVC the url structure goes like

http://example.com/{controller}/{action}/{id}

For each "controller", say http://example.com/blog, there is a BlogController.

But my {controller} 开发者_StackOverflow中文版portion of the url is not decided pre-hand, but it is dynamically determined at run time, how do I create a "dynamic controller" that maps anything to the same controller which then based on the value and determines what to do?

Same thing with {action}, if the {action} portion of my url is also dynamic, is there a way to program this scenario?


Absolutely! You'll need to override the DefaultControllerFactory to find a custom controller if one doesn't exist. Then you'll need to write an IActionInvoker to handle dynamic action names.

Your controller factory will look something like:

public class DynamicControllerFactory : DefaultControllerFactory
{
    private readonly IServiceLocator _Locator;

    public DynamicControllerFactory(IServiceLocator locator)
    {
        _Locator = locator;
    }

    protected override Type GetControllerType(string controllerName)
    {
        var controllerType = base.GetControllerType(controllerName);
            // if a controller wasn't found with a matching name, return our dynamic controller
        return controllerType ?? typeof (DynamicController);
    }

    protected override IController GetControllerInstance(Type controllerType)
    {
        var controller = base.GetControllerInstance(controllerType) as Controller;

        var actionInvoker = _Locator.GetInstance<IActionInvoker>();
        if (actionInvoker != null)
        {
            controller.ActionInvoker = actionInvoker;
        }

        return controller;
    }
}

Then your action invoker would be like:

public class DynamicActionInvoker : ControllerActionInvoker
{
    private readonly IServiceLocator _Locator;

    public DynamicActionInvoker(IServiceLocator locator)
    {
        _Locator = locator;
    }

    protected override ActionDescriptor FindAction(ControllerContext controllerContext,
                                                   ControllerDescriptor controllerDescriptor, string actionName)
    {
            // try to match an existing action name first
        var action = base.FindAction(controllerContext, controllerDescriptor, actionName);
        if (action != null)
        {
            return action;
        }

// @ray247 The remainder of this you'd probably write on your own...
        var actionFinders = _Locator.GetAllInstances<IFindAction>();
        if (actionFinders == null)
        {
            return null;
        }

        return actionFinders
            .Select(f => f.FindAction(controllerContext, controllerDescriptor, actionName))
            .Where(d => d != null)
            .FirstOrDefault();
    }
}

You can see a lot more of this code here. It's an old first draft attempt by myself and a coworker at writing a fully dynamic MVC pipeline. You're free to use it as a reference and copy what you want.

Edit

I figured I should include some background about what that code does. We were trying to dynamically build the MVC layer around a domain model. So if your domain contained a Product class, you could navigate to products\alls to see a list of all products. If you wanted to add a product, you'd navigate to product\add. You could go to product\edit\1 to edit a product. We even tried things like allowing you to edit properties on an entity. So product\editprice\1?value=42 would set the price property of product #1 to 42. (My paths might be a little off, I can't recall the exact syntax anymore.) Hope this helps!


After a little more reflection, there may be a bit simpler way for you to handle the dynamic action names than my other answer. You'll still need to override the default controller factory. I think you could define your route like:

routes.MapRoute("Dynamic", "{controller}/{command}/{id}", new { action = "ProcessCommand" });

Then on your default/dynamic controller you'd have

public ActionResult ProcessCommand(string command, int id)
{
   switch(command)
   {
      // whatever.
   }
}


You need to write your own IControllerFactory (or perhaps derive from DefaultControllerFactory) and then register it with ControllerBuilder.


Iam working with it in .Core but i'll share it's MVC version for all, after that i will share the core version

                case OwnerType.DynamicPage:
                    var dp = mediator.Handle(new Domain.DynamicPages.DynamicPageDtoQuery { ShopId = ShopId, SeoId = seoSearchDto.Id }.AsSingle());
                    if (dp != null)
                    {
                        return GetDynamicPage(dp.Id);
                    }
                    break;

// some codes

    private ActionResult GetDynamicPage(int id)
    {
        var routeObj = new
        {
            action = "Detail",
            controller = "DynamicPage",
            id = id
        };

        var bController = DependencyResolver.Current.GetService<DynamicPageController>();
        SetControllerContext(bController, routeObj);
        return bController.Detail(id);
    }

// and

private void SetControllerContext(ControllerBase controller, object routeObj)
{
    RouteValueDictionary routeValues = new RouteValueDictionary(routeObj);

    var vpd = RouteTable.Routes["Default"].GetVirtualPath(this.ControllerContext.RequestContext, routeValues);



    RouteData routeData = new RouteData();

    foreach (KeyValuePair<string, object> kvp in routeValues)
    {
        routeData.Values.Add(kvp.Key, kvp.Value);
    }

    foreach (KeyValuePair<string, object> kvp in vpd.DataTokens)
    {
        routeData.DataTokens.Add(kvp.Key, kvp.Value);
    }


    routeData.Route = vpd.Route;
    if (routeData.RouteHandler == null)
        routeData.RouteHandler = new MvcRouteHandler();


    controller.ControllerContext = new ControllerContext(this.ControllerContext.HttpContext, routeData, controller);
}
0

上一篇:

下一篇:

精彩评论

暂无评论...
验证码 换一张
取 消

最新问答

问答排行榜