问题
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?
回答1:
Try this detailed article I've written: 3 Ways to Display Views in Your nopCommerce Plugins (Embedded Resource, Theme Override and Custom View Engine)
回答2:
@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
, 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
.
回答3:
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
来源:https://stackoverflow.com/questions/17321898/how-to-override-a-nopcommerce-view-file-with-a-view-file-inside-the-plugin