4

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?

ROMANIA_engineer
  • 54,432
  • 29
  • 203
  • 199
Kevin NP
  • 57
  • 1
  • 4

3 Answers3

4

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

wooncherk
  • 1,364
  • 7
  • 10
  • I'm unable to override an existing core View using your Custom View Engine method. My custom View is never used unless I delete/rename the existing core View. This suggests that my custom View has a lower precedence than the existing core View. So I decided to delete/rename my custom View as well and refresh the page. You can refer to the screenshot here `http://imgur.com/AJgSrnt.jpg` which confirmed that my custom View has the lowest priority. – Twisted Whisper Aug 24 '14 at 11:03
  • I tried theme override method, but I can't make it work (I use the given example: `~/Themes/MyTheme/Views/WidgetsNivoSlider/Nop.Plugin.Widgets.NivoSlider.Views.Wi‌dgetsNivoSlider.PublicInfo.cshtml`) – mems Aug 28 '14 at 11:15
  • @wooncherk I am close but not quite understanding this thread. Would you be so kind as to look at http://stackoverflow.com/questions/26241961/very-close-but-not-quite-able-to-override-core-view-nopcommerce and tell me what I am missing? – GPGVM Oct 07 '14 at 17:44
  • Will this Override `@Html.Partial` before it is rendered ? can you answer my [post](http://stackoverflow.com/questions/42920610/override-html-partial-before-it-is-rendered) ? – Shaiju T Mar 21 '17 at 07:49
4

@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.

Twisted Whisper
  • 1,166
  • 2
  • 15
  • 27
  • whisperer I am close but not quite understanding something. Would you please reference http://stackoverflow.com/questions/26241961/very-close-but-not-quite-able-to-override-core-view-nopcommerce. – GPGVM Oct 07 '14 at 17:45
  • use newLocations.Insert(0, "~/Plugins/CustomExtension/Views/Admin/{1}/{0}.cshtml"); instead of newLocations.Insert(0, "~/Administration/CustomExtension/Views/{1}/{0}.cshtml"); – RouR Oct 27 '14 at 15:48
  • I have successfully implemented the method that Wolf, Woon, and you have discussed, but I have run into an interesting problem. Long story short my CustomViewEngine is working too well, www.mysite.com is throwing an error which is basically the Front end WebController trying to digest the Admin View(Index). However, when I go to www.mysite.com/admin everything is working great as it should be... Essentially my question to you is how do you deal with concurrent view names? In my case Nop.Admin/Home/Index.cshtml and Nop.Web/Home/Index.cshtml? – pax Nov 11 '14 at 20:31
  • This shouldn't be throwing errors. Administration is an area which means admin views always get resolved first. Did you mess up the routing? Try renaming both `index.cshtml` to `index_bak.cshtml`. Refresh the page and an exception should be thrown. Note on the order views are being searched. Admin views should always be at the top. – Twisted Whisper Nov 12 '14 at 19:11
  • does this work also for nop.web views? I tried to implement it but viewPath and masterPath are returned empty string. What could it be wrong? – Emil Aug 28 '15 at 12:21
  • Will this Override `@Html.Partial` before it is rendered ? can you answer my [post](http://stackoverflow.com/questions/42920610/override-html-partial-before-it-is-rendered) ? – Shaiju T Mar 21 '17 at 07:49
2

Twisted Whisper has the correct answer but I thought I would share a link to a blog post that discusses this more in depth (talks more about how the custom view engine works and other advantages of using this technique) seen here:

Click here for In Depth Discussion Post

Alex Wolf
  • 111
  • 3
  • I wished I found your blog post sooner. It took me a few hours to dig under the covers before coming up with a solution since I'm fairly new to nopCommerce. – Twisted Whisper Aug 27 '14 at 17:17
  • @alex wolf....Your post was most helpful but I am still not understanding something. If you could please review http://stackoverflow.com/questions/26241961/very-close-but-not-quite-able-to-override-core-view-nopcommerce and let me know what I am doing wrong. – GPGVM Oct 07 '14 at 17:46