ASP.NET MVC 模块
ASP.NET MVC Modules
我需要创建一个 nuget 包,其中将包含共享视图、控制器、js 和 css 文件,以便在多个其他项目中使用。本质上是一组模块化的东西,例如可以放入其他站点项目的结帐或搜索页面。
到目前为止,我所做的所有研究都指向使用带有 RazorGenerator 的预编译视图,但对控制器、js 和 css 文件的了解不多。
理想情况下,模块的视图和其他文件应该能够被使用宿主项目覆盖,但文件本身不应在宿主项目中直接编辑。很像添加其他 nuget 包时引用的 dll。
到目前为止,我发现的关于此类主题的答案和帖子似乎有些过时。
是否有更简洁、现代的解决方案来创建 ASP.NET MVC 模块 nuget 包,以便能够跨项目共享完全工作的页面?
控制器
使用区域并注册这些区域。可能这不是本机支持的,您可能需要覆盖 mvc4 中的某些部分。看看:
- How do I register a controller that has been created in an AREA
- http://netmvc.blogspot.be/2012/03/aspnet-mvc-4-webapi-support-areas-in.html
只要加载了 dll,您始终可以使用反射(在应用程序启动时)注册所有 Controller
的子classes classes。
剃刀
预编译是可能的,但只有在 dotnet 核心中才真正值得推荐,因为它是那里的第一个 class 公民。
您还可以将视图作为内容添加到项目中。
缺点:
- 更新时它会覆盖视图(如果您更改它们,您将丢失更改)
优点:
- 更新时,您可以合并 git
中的两个更改
- 轻松更改现有的剃须刀页面
我找到了适合我们的解决方案。我们还没有完全实施这一点,所以一些不可预见的问题可能仍然会出现。
首先,创建一个 MVC 项目,其中包含所需的视图、控制器、javascript 等,以满足您的包要求。每个静态文件和视图必须在项目中设置为嵌入式资源。
然后,添加 class 以在虚拟路径提供程序上提供这些文件。这将允许消费项目访问静态文件和视图,就好像它们在同一个项目中一样。
要启用自定义路由,需要 RouteBase
class 的实现。此实现需要接受虚拟路由所基于的 string
属性 以允许主机应用所需的任何路由前缀。对于我们的示例,属性 将默认为 Booking 与我们项目中的相关视图架构相匹配。
RouteBase
实现和 VirtualPath
class 都将在设置方法中实例化。这将允许消费项目调用单一方法来设置预订引擎。此方法将采用站点路由集合和动态路由 属性 来附加自定义路由。该方法还将 VirtualPathProvider
注册到 HostingEnvironment
对象。
消费主机也可以覆盖视图和任何其他静态文件,只需在主机项目中的某个位置放置一个文件,该文件与预订引擎中文件或视图的路径相匹配。
一些代码示例
RouteBase
如果传入路由与虚拟路由匹配,returns 更正路由值的方法。
public override RouteData GetRouteData(HttpContextBase httpContext)
{
RouteData result = null;
// Trim the leading slash
var path = httpContext.Request.Path.Substring(1);
// Get the page that matches.
var page = GetPageList(httpContext)
.Where(x => x.VirtualPath.Equals(path))
.FirstOrDefault();
if (page != null)
{
result = new RouteData(this, new MvcRouteHandler());
// Optional - make query string values into route values.
AddQueryStringParametersToRouteData(result, httpContext);
result.Values["controller"] = page.Controller;
result.Values["action"] = page.Action;
}
// IMPORTANT: Always return null if there is no match.
// This tells .NET routing to check the next route that is registered.
return result;
}
RouteBase
NuGet 包路由映射的虚拟路由。使用动态虚拟路径字符串和对真实控制器和操作名称的引用创建的新 PageInfo
个对象。然后将它们存储在 http 上下文缓存中。
private IEnumerable<PageInfo> GetPageList(HttpContextBase httpContext)
{
string key = "__CustomPageList";
var pages = httpContext.Cache[key];
if (pages == null)
{
lock (synclock)
{
pages = httpContext.Cache[key];
if (pages == null)
{
pages = new List<PageInfo>()
{
new PageInfo()
{
VirtualPath = string.Format("{0}/Contact", BookingEngine.Route),
Controller = "Home",
Action = "Contact"
},
};
httpContext.Cache.Insert(
key: key,
value: pages,
dependencies: null,
absoluteExpiration: System.Web.Caching.Cache.NoAbsoluteExpiration,
slidingExpiration: TimeSpan.FromMinutes(1),
priority: System.Web.Caching.CacheItemPriority.NotRemovable,
onRemoveCallback: null);
}
}
}
return (IEnumerable<PageInfo>)pages;
}
预订引擎class 设置方法,它完成组装所需的所有实例化。
public class BookingEngine
{
public static string Route = "Booking";
public static void Setup(RouteCollection routes, string route)
{
Route = route;
HostingEnvironment.RegisterVirtualPathProvider(
new EmbeddedVirtualPathProvider());
routes.Add(
name: "CustomPage",
item: new CustomRouteController());
}
}
嵌入式虚拟文件
public override CacheDependency GetCacheDependency(string virtualPath, virtualPathDependencies, DateTime utcStart)
{
string embedded = _GetEmbeddedPath(virtualPath);
// not embedded? fall back
if (string.IsNullOrEmpty(embedded))
return base.GetCacheDependency(virtualPath,
virtualPathDependencies, utcStart);
// there is no cache dependency for embedded resources
return null;
}
public override bool FileExists(string virtualPath)
{
string embedded = _GetEmbeddedPath(virtualPath);
// You can override the embed by placing a real file at the virtual path...
return base.FileExists(virtualPath) || !string.IsNullOrEmpty(embedded);
}
public override VirtualFile GetFile(string virtualPath)
{
// You can override the embed by placing a real file at the virtual path...
if (base.FileExists(virtualPath))
return base.GetFile(virtualPath);
string embedded = _GetEmbeddedPath(virtualPath);
if (string.IsNullOrEmpty(embedded))
return null;
return new EmbeddedVirtualFile(virtualPath, GetType().Assembly
.GetManifestResourceStream(embedded));
}
private string _GetEmbeddedPath(string path)
{
if (path.StartsWith("~/"))
path = path.Substring(1);
path = path.Replace(BookingEngine.Route, "/");
//path = path.ToLowerInvariant();
path = System.Reflection.Assembly.GetExecutingAssembly().GetName().Name + path.Replace('/', '.');
// this makes sure the "virtual path" exists as an embedded resource
return GetType().Assembly.GetManifestResourceNames()
.Where(o => o == path).FirstOrDefault();
}
嵌套虚拟文件Class
public class EmbeddedVirtualFile : VirtualFile
{
private Stream _stream;
public EmbeddedVirtualFile(string virtualPath,
Stream stream) : base(virtualPath)
{
if (null == stream)
throw new ArgumentNullException("stream");
_stream = stream;
}
public override Stream Open()
{
return _stream;
}
}
我们使用的很多代码都来自以下链接
嵌入文件 - https://www.ianmariano.com/2013/06/11/embedded-razor-views-in-mvc-4/
RouteBase 实现 -
我需要创建一个 nuget 包,其中将包含共享视图、控制器、js 和 css 文件,以便在多个其他项目中使用。本质上是一组模块化的东西,例如可以放入其他站点项目的结帐或搜索页面。
到目前为止,我所做的所有研究都指向使用带有 RazorGenerator 的预编译视图,但对控制器、js 和 css 文件的了解不多。
理想情况下,模块的视图和其他文件应该能够被使用宿主项目覆盖,但文件本身不应在宿主项目中直接编辑。很像添加其他 nuget 包时引用的 dll。
到目前为止,我发现的关于此类主题的答案和帖子似乎有些过时。
是否有更简洁、现代的解决方案来创建 ASP.NET MVC 模块 nuget 包,以便能够跨项目共享完全工作的页面?
控制器
使用区域并注册这些区域。可能这不是本机支持的,您可能需要覆盖 mvc4 中的某些部分。看看:
- How do I register a controller that has been created in an AREA
- http://netmvc.blogspot.be/2012/03/aspnet-mvc-4-webapi-support-areas-in.html
只要加载了 dll,您始终可以使用反射(在应用程序启动时)注册所有 Controller
的子classes classes。
剃刀
预编译是可能的,但只有在 dotnet 核心中才真正值得推荐,因为它是那里的第一个 class 公民。
您还可以将视图作为内容添加到项目中。
缺点:
- 更新时它会覆盖视图(如果您更改它们,您将丢失更改)
优点:
- 更新时,您可以合并 git 中的两个更改
- 轻松更改现有的剃须刀页面
我找到了适合我们的解决方案。我们还没有完全实施这一点,所以一些不可预见的问题可能仍然会出现。
首先,创建一个 MVC 项目,其中包含所需的视图、控制器、javascript 等,以满足您的包要求。每个静态文件和视图必须在项目中设置为嵌入式资源。
然后,添加 class 以在虚拟路径提供程序上提供这些文件。这将允许消费项目访问静态文件和视图,就好像它们在同一个项目中一样。
要启用自定义路由,需要 RouteBase
class 的实现。此实现需要接受虚拟路由所基于的 string
属性 以允许主机应用所需的任何路由前缀。对于我们的示例,属性 将默认为 Booking 与我们项目中的相关视图架构相匹配。
RouteBase
实现和 VirtualPath
class 都将在设置方法中实例化。这将允许消费项目调用单一方法来设置预订引擎。此方法将采用站点路由集合和动态路由 属性 来附加自定义路由。该方法还将 VirtualPathProvider
注册到 HostingEnvironment
对象。
消费主机也可以覆盖视图和任何其他静态文件,只需在主机项目中的某个位置放置一个文件,该文件与预订引擎中文件或视图的路径相匹配。
一些代码示例
RouteBase
如果传入路由与虚拟路由匹配,returns 更正路由值的方法。
public override RouteData GetRouteData(HttpContextBase httpContext)
{
RouteData result = null;
// Trim the leading slash
var path = httpContext.Request.Path.Substring(1);
// Get the page that matches.
var page = GetPageList(httpContext)
.Where(x => x.VirtualPath.Equals(path))
.FirstOrDefault();
if (page != null)
{
result = new RouteData(this, new MvcRouteHandler());
// Optional - make query string values into route values.
AddQueryStringParametersToRouteData(result, httpContext);
result.Values["controller"] = page.Controller;
result.Values["action"] = page.Action;
}
// IMPORTANT: Always return null if there is no match.
// This tells .NET routing to check the next route that is registered.
return result;
}
RouteBase
NuGet 包路由映射的虚拟路由。使用动态虚拟路径字符串和对真实控制器和操作名称的引用创建的新 PageInfo
个对象。然后将它们存储在 http 上下文缓存中。
private IEnumerable<PageInfo> GetPageList(HttpContextBase httpContext)
{
string key = "__CustomPageList";
var pages = httpContext.Cache[key];
if (pages == null)
{
lock (synclock)
{
pages = httpContext.Cache[key];
if (pages == null)
{
pages = new List<PageInfo>()
{
new PageInfo()
{
VirtualPath = string.Format("{0}/Contact", BookingEngine.Route),
Controller = "Home",
Action = "Contact"
},
};
httpContext.Cache.Insert(
key: key,
value: pages,
dependencies: null,
absoluteExpiration: System.Web.Caching.Cache.NoAbsoluteExpiration,
slidingExpiration: TimeSpan.FromMinutes(1),
priority: System.Web.Caching.CacheItemPriority.NotRemovable,
onRemoveCallback: null);
}
}
}
return (IEnumerable<PageInfo>)pages;
}
预订引擎class 设置方法,它完成组装所需的所有实例化。
public class BookingEngine
{
public static string Route = "Booking";
public static void Setup(RouteCollection routes, string route)
{
Route = route;
HostingEnvironment.RegisterVirtualPathProvider(
new EmbeddedVirtualPathProvider());
routes.Add(
name: "CustomPage",
item: new CustomRouteController());
}
}
嵌入式虚拟文件
public override CacheDependency GetCacheDependency(string virtualPath, virtualPathDependencies, DateTime utcStart)
{
string embedded = _GetEmbeddedPath(virtualPath);
// not embedded? fall back
if (string.IsNullOrEmpty(embedded))
return base.GetCacheDependency(virtualPath,
virtualPathDependencies, utcStart);
// there is no cache dependency for embedded resources
return null;
}
public override bool FileExists(string virtualPath)
{
string embedded = _GetEmbeddedPath(virtualPath);
// You can override the embed by placing a real file at the virtual path...
return base.FileExists(virtualPath) || !string.IsNullOrEmpty(embedded);
}
public override VirtualFile GetFile(string virtualPath)
{
// You can override the embed by placing a real file at the virtual path...
if (base.FileExists(virtualPath))
return base.GetFile(virtualPath);
string embedded = _GetEmbeddedPath(virtualPath);
if (string.IsNullOrEmpty(embedded))
return null;
return new EmbeddedVirtualFile(virtualPath, GetType().Assembly
.GetManifestResourceStream(embedded));
}
private string _GetEmbeddedPath(string path)
{
if (path.StartsWith("~/"))
path = path.Substring(1);
path = path.Replace(BookingEngine.Route, "/");
//path = path.ToLowerInvariant();
path = System.Reflection.Assembly.GetExecutingAssembly().GetName().Name + path.Replace('/', '.');
// this makes sure the "virtual path" exists as an embedded resource
return GetType().Assembly.GetManifestResourceNames()
.Where(o => o == path).FirstOrDefault();
}
嵌套虚拟文件Class
public class EmbeddedVirtualFile : VirtualFile
{
private Stream _stream;
public EmbeddedVirtualFile(string virtualPath,
Stream stream) : base(virtualPath)
{
if (null == stream)
throw new ArgumentNullException("stream");
_stream = stream;
}
public override Stream Open()
{
return _stream;
}
}
我们使用的很多代码都来自以下链接
嵌入文件 - https://www.ianmariano.com/2013/06/11/embedded-razor-views-in-mvc-4/
RouteBase 实现 -