Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

How to override a nopcommerce view file with a view file inside the plugin?

I am trying to override the nopcommerce View that is located in:

Nop.Admin/Views/Category/Tree.cshtml  

with a view that I have developed in my plugin folder:

Views/Misc/Tree.cshtml

How can I do it?

like image 810
Kevin NP Avatar asked Dec 27 '22 01:12

Kevin NP


2 Answers

Try this detailed article I've written: 3 Ways to Display Views in Your nopCommerce Plugins (Embedded Resource, Theme Override and Custom View Engine)

like image 137
wooncherk Avatar answered May 03 '23 02:05

wooncherk


@wooncherk's Custom View Engine is excellent in preparing our Views to be overriden with ease in the future. However it falls short when it comes to overriding existing core Views because nopCommerce gives Administration Views top priority than our custom Views. This can be seen in the virtual method GetPath() of Nop.Web.Framework.Themes.ThemeableVirtualPathProviderViewEngine.cs. For those who is wondering, ThemeableVirtualPathProviderViewEngine is the class inherited by ThemeableRazorViewEngine which in turn inherited by @wooncherk's CustomViewEngine class.

ThemeableVirtualPathProviderViewEngine Referring to the screenshot above on ThemeableVirtualPathProviderViewEngine, as pointed out by the arrows, the two lines confirmed that Administration Views are always given higher precedence than our custom Views

I managed to extend @wooncherk's Custom View Engine method to also cater for overriding existing Administration core Views. This involves overriding and copying over the virtual method GetPath() into the CustomViewEngine class. At this point it seems logical to remove the two culprit lines or even the entire little hack code block, but don't, it will result in an exception

The model item passed into the dictionary is of type 'System.Collections.Generic.List`1[Nop.Admin.Models.Cms.RenderWidgetModel]', but this dictionary requires a model item of type 'System.Collections.Generic.List`1[Nop.Web.Models.Cms.RenderWidgetModel]'.

The new CustomViewEngine will be as follows:

public class CustomViewEngine: ThemeableRazorViewEngine {
    private readonly string[] _emptyLocations = null;

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

        ViewLocationFormats = new[] {
            "~/Administration/CustomExtension/Views/{1}/{0}.cshtml",
            "~/Administration/CustomExtension/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/CustomExtension/Views/{1}/{0}.cshtml");
            newLocations.Insert(0, "~/Administration/CustomExtension/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);
    }
}

Note that two lines are added below the culprit lines to give our custom Views higher precedence.


Finally, we need to modify the RouteProvider.cs

public class RouteProvider : IRouteProvider {
    public void RegisterRoutes(RouteCollection routes) {
        //Insert our CustomViewEngine into the top of the System.Web.Mvc.ViewEngines.Engines Collection to be given a higher precedence
        System.Web.Mvc.ViewEngines.Engines.Insert(0, new CustomViewEngine());
    }

    public int Priority {
        get {
            return 1;
        }
    }
}

That's about it. Now place your custom Views/Partial Views into the View Locations, in this case they are

~/Administration/CustomExtension/Views/{1}/{0}.cshtml
~/Administration/CustomExtension/Views/Shared/{0}.cshtml

whereby {1} is the name of the Controller you are overriding and {0} is the name of the View/Partial View you are overrding.

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.

like image 44
Twisted Whisper Avatar answered May 03 '23 04:05

Twisted Whisper