简体   繁体   中英

ASP.NET MVC Modules

I need to create a nuget package which will contain shared views, controllers, js, and css files to be used across multiple other projects. Essentially a modular set of things like checkout or search pages that can be dropped into other site projects.

All the research I've done so far points to utilizing precompiled views with RazorGenerator but doesn't say much about the controllers, js, and css files.

Ideally a module's views and other files should be able to be overridden by the consuming host project but the files themselves should not be directly editable within the host project. Much like dlls referenced when other nuget packages are added.

The answers and posts about this type of subject I've found so far seem a bit dated.

Is there a cleaner, modern solution for creating an ASP.NET MVC module nuget package so that fully working pages are able to be shared across projects?

Controllers

Use area's and register those area's. Possibly this is not natively supported and you might need to overwrite some parts in mvc4. look at:

As long as the dll's are loaded in you can always register all classes that are subclasses of Controller with reflection (on application startup).

Razor

Precompiling is possible, but only really adviseable in dotnet core since it is a first class citizen there.

You could also add the views as content which is injected into the project.

Downside:

  • On update it overwrites the views (if you changed them you lost changes)

Pros:

  • On update you can merge both changes in git
  • Easily change the already existing razor pages

I found a solution that works for us. We have not yet fully implemented this so some unforeseen issues may still arise.

First, create an MVC project with the needed views, controllers, javascript, etc needed to match your package requirements. Each static file and view must be set as embedded resources in the project.

Then, add a class to serve these files on a virtual path provider. This will allow a consuming project to access the static files and views as if they were within the same project.

To enable custom routing an implementation of the RouteBase class will be required. This implementation needs to accept a string property for which virtual routes are based to allow the host to apply whichever route prefix is desired. For our example, the property will default to Booking with the associated architecture of views within our project to match.

Both the RouteBase implementation and the VirtualPath class will be instantiated within a setup method. This will allow a consuming project to call a single method to setup the booking engine. This method will take in the sites Route Collection and dynamic route property to append the custom routes. The method will also register the VirtualPathProvider to the HostingEnvironment object.

The consuming host can also override views and any other static file by simply having a file in a location within the host project that matches the path of the file or view in the booking engine.

Some Code Examples

RouteBase method which returns correct route values if an incoming route matches a virtual route.

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 Virtual Route to NuGet Package Route mapping. New PageInfo objects created with dynamic virtual path string and references to real controller and action names. These are then stored in the http context cache.

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 class setup method which does all the instantiating needed for the assembly.

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

EmbeddedVirtualFile

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

Nested Virtual File 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;
    }
}

A lot of the code we are using comes from the following links

Embedded Files - https://www.ianmariano.com/2013/06/11/embedded-razor-views-in-mvc-4/

RouteBase implementation - Multiple levels in MVC custom routing

The technical post webpages of this site follow the CC BY-SA 4.0 protocol. If you need to reprint, please indicate the site URL or the original address.Any question please contact:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM