【问题标题】:ASP.NET MVC ModulesASP.NET MVC 模块
【发布时间】:2017-10-03 13:43:04
【问题描述】:

我需要创建一个 nuget 包,其中包含要在多个其他项目中使用的共享视图、控制器、js 和 css 文件。本质上是一组模块化的东西,例如可以放入其他网站项目的结帐或搜索页面。

到目前为止,我所做的所有研究都指向使用带有 RazorGenerator 的预编译视图,但对控制器、js 和 css 文件并没有多说。

理想情况下,模块的视图和其他文件应该能够被使用宿主项目覆盖,但文件本身不应在宿主项目中直接编辑。很像添加其他 nuget 包时引用的 dll。

到目前为止,我发现的有关此类主题的答案和帖子似乎有些过时了。

是否有一种更简洁、更现代的解决方案来创建 ASP.NET MVC 模块 nuget 包,以便能够跨项目共享完整的工作页面?

【问题讨论】:

  • 好问题,但对 StackOverflow 来说太宽泛了。尝试将其分解为几个单独的问题。我过去曾对此进行过调查,得出的结论是,事情并不那么简单。但我认为,如果有人主动创建一个开源项目,将所有核心功能放入一个可重用的软件和 Visual Studio 插件和加载项中以使这样的事情易于管理,那将是可能的。
  • @NightOwl888 我不同意,这是一个特定的问题。和简单的解决方案让控制器得到IHttpControllerFactory 的认可,尽管 Joshua 可以对 MVC 框架本身进行一些更深入的研究,并且不需要问这样一个(看似)广泛的问题
  • @JoelHarkes - 您的回答没有涵盖 OP 要求的 css 或 js 文件,也没有解决制作 NuGet 包的问题。唯一合乎逻辑的做法是将内容文件制作成嵌入式资源(因为 NuGet 可以部署代码文件,但不够聪明,无法合并代码文件中的更改),然后您正在谈论深入研究 MVC 的更多扩展来制作所有这些工作(因此有多个问题)。控制器很容易移植到其他 DLL,但内容文件和视图需要更多的努力才能确保它们可以被 MVC 正确加载并提供服务。

标签: c# asp.net asp.net-mvc asp.net-mvc-4 nuget-package


【解决方案1】:

我找到了适合我们的解决方案。我们尚未完全实施这一点,因此可能仍会出现一些无法预料的问题。

首先,创建一个 MVC 项目,其中包含满足您的包要求所需的视图、控制器、javascript 等。每个静态文件和视图都必须在项目中设置为嵌入式资源

然后,添加一个类以在虚拟路径提供程序上提供这些文件。这将允许消费项目访问静态文件和视图,就好像它们在同一个项目中一样。

要启用自定义路由,需要实现 RouteBase 类。此实现需要接受 string 属性,虚拟路由所基于的属性允许主机应用所需的任何路由前缀。对于我们的示例,该属性将默认为 Booking,并与我们项目中的相关视图架构相匹配。

RouteBase 实现和VirtualPath 类都将在设置方法中实例化。这将允许消费项目调用单个方法来设置预订引擎。此方法将采用站点路由集合和动态路由属性来附加自定义路由。该方法还将VirtualPathProvider注册到HostingEnvironment对象。

消费主机也可以覆盖视图和任何其他静态文件,只需在主机项目中与预订引擎中的文件或视图路径匹配的位置放置一个文件即可。

一些代码示例

RouteBase 方法,如果传入路由与虚拟路由匹配,则返回正确的路由值。

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;
}

Booking Engine 类设置方法,它执行程序集所需的所有实例化。

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();
}

嵌套虚拟文件类

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 实现 - Multiple levels in MVC custom routing

【讨论】:

    【解决方案2】:

    控制器

    使用区域并注册这些区域。可能这不是本机支持的,您可能需要覆盖 mvc4 中的某些部分。看看:

    只要加载了 dll,您就可以通过反射(在应用程序启动时)注册所有属于 Controller 子类的类。

    剃须刀

    预编译是可能的,但只有在 dotnet core 中才真正值得推荐,因为它是那里的一等公民。

    您还可以将视图作为内容添加到项目中。

    缺点:

    • 更新时会覆盖视图(如果您更改了它们,则会丢失更改)

    优点:

    • 更新时可以合并 git 中的两个更改
    • 轻松更改已经存在的剃须刀页面

    【讨论】:

    • Joel 和@NightOwl888,经过一些额外的研究后,我发现了一些可以满足我们目前需要的东西的组合。如果您对实现感到好奇,请参阅下面的答案。
    猜你喜欢
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    相关资源
    最近更新 更多