Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

Very Close but not quite able to override core view nopcommerce

I have been reading some very good post from Wooncherk, Twisted Whisperer, and Alex Wolf. Their articles respectively (Wooncherk), (Twisted Whisperer), and (Alex Wolf) have been extremely informative but alas I'm just not as smart as the rest of the SO community and can't quite piece together what I'm missing.

I am overriding a core view in the admin area...specifically the order edit view. The behavior I am seeing is that it does NOT hit the controller in my plugin but it DOES display my custom view. Problem is that the custom view is in the Admin project and this really confuses me. How can I have a self-contained plugin yet have to install my custom view into the core Admin area?

I thought, perhaps incorrectly, that what would happen is that my controller would be hit first when paths are being searched because of my higher defined priority.

So following the directions here is my code.

CustomViewEngine:


private readonly string[] _emptyLocations = null;

    public CustomViewEngine()
    {
        PartialViewLocationFormats = new[]
        {
            "~/Administration/MyExtension/Views/{1}/{0}.cshtml",
            "~/Administration/MyExtension/Views/Shared/{0}.cshtml"
        };

        ViewLocationFormats = new[]
        {
            "~/Administration/MyExtension/Views/{1}/{0}.cshtml",
            "~/Administration/MyExtension/Views/Shared/{0}.cshtml"
        };
    }


    protected override string GetPath(ControllerContext controllerContext, string[] locations, string[] areaLocations, string locationsPropertyName, string name, string controllerName, string theme, string cacheKeyPrefix, bool useCache, out string[] searchedLocations) 
    {
        searchedLocations = _emptyLocations;
        if (string.IsNullOrEmpty(name)) 
        {
            return string.Empty;
        }

        string areaName = GetAreaName(controllerContext.RouteData);

        //little hack to get nop's admin area to be in /Administration/ instead of /Nop/Admin/ or Areas/Admin/
        if (!string.IsNullOrEmpty(areaName) && areaName.Equals("admin", StringComparison.InvariantCultureIgnoreCase)) 
        {
            var newLocations = areaLocations.ToList();
            newLocations.Insert(0, "~/Administration/Views/{1}/{0}.cshtml");
            newLocations.Insert(0, "~/Administration/Views/Shared/{0}.cshtml");

            //Insert your custom View locations to the top of the list to be given a higher precedence
            newLocations.Insert(0, "~/Administration/MyExtension/Views/{1}/{0}.cshtml");
            newLocations.Insert(0, "~/Administration/MyExtension/Views/Shared/{0}.cshtml");

            areaLocations = newLocations.ToArray();
        }

        bool flag = !string.IsNullOrEmpty(areaName);
        List<ViewLocation> viewLocations = GetViewLocations(locations, flag ? areaLocations : null);

        if (viewLocations.Count == 0) 
        {
            throw new InvalidOperationException(string.Format(CultureInfo.CurrentCulture, "Properties cannot be null or empty.", new object[] { locationsPropertyName }));
        }

        bool flag2 = IsSpecificPath(name);
        string key = CreateCacheKey(cacheKeyPrefix, name, flag2 ? string.Empty : controllerName, areaName, theme);

        if (useCache) 
        {
            var cached = ViewLocationCache.GetViewLocation(controllerContext.HttpContext, key);
            if (cached != null) 
            {
                return cached;
            }
        }

        if (!flag2) 
        {
            return GetPathFromGeneralName(controllerContext, viewLocations, name, controllerName, areaName, theme, key, ref searchedLocations);
        }

    return GetPathFromSpecificName(controllerContext, name, key, ref searchedLocations);
}

Route Provider See Comments Trying to override the core url ~/Admin/Order/Edit/1


public void RegisterRoutes(RouteCollection routes)
    {
        ViewEngines.Engines.Insert(0, new CustomViewEngine());

//the articles above did NOT mention adding a path but it seemed like I needed to in order for my override path to be included???
        routes.MapRoute("Plugin...OrderDetailsOverride", "Order/Edit/{id}",
             new { controller = "MyOrder", action = "Edit" },
             new { id = @"\d+" },
             new[] { "MyPlugin.Controllers" }
        );            

    }

    public int Priority
    {
        get
        {
            return 1;
        }
    }

I was getting a 404 error and carefully rereading Twisted Whisperer he(I assume he) states:

For example, if you are overrding Nop.Admin/Views/Category/Tree.cshtml, place your custom Tree.cshtml in Nop.Admin/CustomExtension/Views/Category/Tree.cshtml

Well if I interpret that literally I would do this in the CORE ADMIN project:

enter image description here

I obviously did this and it worked...sorta.

So in summary my questions / issues are:

  1. My Order Override Controller in the plugin isn't hit....(not interested in using ActionFilters as they don't give me what I need...I don't think).

  2. The view in my plugin isn't hit but the one added to the admin project is hit?

  3. Somewhat related to 2. If two is correct then how is that a viable plugin solution? For a production push, updates, etc. I would potentially have to touch the NopCommerce core projects....well why bother with a plugin then?

Now the NOP guys as well as SO Community are far wiser than I so I'm sure I just don't understand properly.

like image 367
GPGVM Avatar asked Jan 09 '23 16:01

GPGVM


1 Answers

If I read your question correctly, you are creating a plugin and trying to do all the plumbings in this plugin without touching the core Admin files at all. Then you have to put all those files in your plugin project and not in Nop.Admin

From the looks of it, you are overriding both the Controller and View of the back-end (Admin area). The SO post that you quote in which my answer is only catered for overriding Admin Views, not Controllers. Overriding Controller in the Admin area is different than overriding View, and even more different if you're overriding Controllers of the front-end (assuming SEO friendly URL is involved).

Overriding a Controller is essentially asking ASP.NET MVC to use your custom Controller to serve request instead of the original Controller. Requests are directed to Controllers through Routes. So overriding a Controller is just simply manipulating Routes to direct requests to your custom Controller.

ASP.NET MVC keeps a universal lookup table that contains Routes and is called the RouteTable.Routes. A Route contains data of

  1. the URL Pattern
  2. which Controller to forward incoming request to

Every time there's an incoming request, ASP.NET MVC will search RouteTable.Routes from top to bottom to find a route whose URL Pattern matches the request's URL. The first matching route will be used to direct the request to the matching route's Controller. There are probably several matching routes in the table but only the first will be used. Therefore, we can say the route at the top of the table has the highest precedence. Hence, the key of overriding an existing Controller is to make sure that the route pointing to your custom Controller is registered earlier than the route pointing to the original Controller

In nopCommerce, you do not need to directly manipulate the RouteTable.Routes lookup table. Normally you would just register your custom Controller route in the RegisterRoutes method of a class implementing the IRouteProvider interface with the Priority higher than the original Controller route (not applicable for SEO friendly URLs Area routes). The higher the Priority, the earlier the route is registered and therefore the higher it will be positioned in the lookup table. However, the back-end of nopCommerce is actually an ASP.NET MVC Area named Admin. This fact made it tricky to override routes the usual way. This is because, in the global.asax

//global.asax
protected void Application_Start() {
    //.... Omitted for brevity

    //Registering some regular mvc stuff
    //The two lines below populate the RouteTable.Routes
    AreaRegistration.RegisterAllAreas(); //add Area routes into the lookup table first such as the "Admin" area
    RegisterRoutes(RouteTable.Routes);  //followed by adding routes of classes implementing **IRouteProvider**

    //.... Omitted for brevity
}

As you can see above, area routes are always registered first and therefore has the highest precedence. Hence, in IRouteProvider, no matter how high you set the Priority, it will still be lower than area routes. Because of this, you will have to manipulate the RouteTable.Routes directly. In your IRouteProvider

public void RegisterRoutes(RouteCollection routes)
{
    ViewEngines.Engines.Insert(0, new CustomViewEngine());

    var route = routes.MapRoute("Plugin...OrderDetailsOverride",
         "Admin/Order/Edit/{id}",
         new { controller = "MyOrder", action = "Edit" area = "Admin" }, //notice 'area="Admin"' is added
         new { id = @"\d+" },
         new[] { "MyPlugin.Controllers" }
    );
    routes.remove(route); //remove your route from the RouteTable.Routes
    routes.insert(0, route); //only to add it back again to the top of RouteTable.Routes, above all the routes that have been registered earlier
}

public int Priority
{
    get
    {
        return 1;
    }
}

This is to override Controller. Now to overriding View. Slight adjustment to the CustomViewEngine (Make sure your CustomViewEngine is in your plugin, not in Nop.Admin)

//Insert your custom View locations to the top of the list to be given a higher precedence
newLocations.Insert(0, "~/Plugins/MyPlugin/Views/Admin/{1}/{0}.cshtml");
newLocations.Insert(0, "~/Plugins/MyPlugin/Views/Admin/Shared/{0}.cshtml");

You can actually remove the constructor of CustomViewEngine. The constructor is not necessary if you have the two lines above in the GetPath method.

like image 128
Twisted Whisper Avatar answered Jan 12 '23 07:01

Twisted Whisper