Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

MVC 6 and DotNet Core? #394

Open
joeaudette opened this issue Jun 3, 2015 · 30 comments
Open

MVC 6 and DotNet Core? #394

joeaudette opened this issue Jun 3, 2015 · 30 comments

Comments

@joeaudette
Copy link

Hi,
Now that Visual Studio 2015 RC is available, I've begun working on porting an application to MVC 6 and .net core framework.

As my app currently depends on MvcSiteMapProvider, I'm wondering if you guys are planning to support MVC 6/.net core framework, and if so any idea when a NuGet will be available for that?

Thanks,
Joe Audette

@NightOwl888
Copy link
Collaborator

Planning...yes.

However, I don't have a lot of time to dedicate to MvcSiteMapProvider at present. I also have a policy not to install any pre-release software on my dev boxes, so the only option at present is to set up a virtual machine (which obviously is more work to setup than waiting for the official release of VS2015).

I took a really quick look and it seems that the IControllerFactory interface has changed pretty significantly, which will mean significant changes to the AuthorizeAttributeAclModule (or perhaps even a completely different authorization module) to accommodate MVC6. The IDependencyResolver interface is gone, so there will also need to be some rework to work out how to plug in DI (for those that use it, anyway).

I am guessing there are other changes to make it function as well, but fortunately there aren't very many places where MvcSiteMapProvider and MVC/ASP.NET touch and we already have a conditional compilation symbol scheme to support conditional pieces of code, hopefully it won't be too involved.

Anyway, I just pushed an mvc6 branch and I am willing to accept pull requests for work on MVC 6 on that branch in case anyone wants to jump ahead and start working on it. Of course, it will be best starting from the internal DI container (with IControllerFactory), then fix the MvcSiteMapProvider project to be compatible with MVC 6, fix the build, then tackle the external DI containers last.

@maartenba
Copy link
Owner

Next to that, all of System.Web has disappeared so we will effectively have to create a separate version of MvcSiteMapProvider. PR's welcome :)

@joeaudette
Copy link
Author

Yeah this is a very big transition for ASP.NET, probably the biggest transition in the whole history of asp.net. Actually System.Web is still existing but only in the desktop framework not in the new light weight cross platform .net core framework, which of course is what the cool kids will want to use. System.Configuration is also gone but there is a new lightweight flexible config system that can be used instead.

There is also a new DI Container built in and used by default when you create an asp.net 5/mvc 6 project, though of course it can be replaced with other DI easily. But I would suggest use that as the default instead of an internal DI. As an aside, on another issue thread you mentioned about the Dependency Injection book by Mark Seemann. I took your advice and read that book and really got a lot out of it so thanks for that.

So basically asp.net 5/mvc 6 will run on the core framework but anyone who wants to continue using WebForms or anything form System.Web or System.Configuration will be stuck with the desktop version.

The core framework is also bin deployable so it will be possible to upgrade to new versions without concern for what version is installed at the web host.

The MVC and Web API frameworks have been consolidated so they both use the same controllers. Most previous functionality should still exist but namespaces may have changed. Actually while there is still a base class for controllers, you are not even forced to use it, it is possible to make controllers without it but you still need the convention to name the class with the word Controller like MyController.

instead of .csproj files we now have project.json files and compilation happens at once in memory when files are edited rather than requiring a rebuild. It is very fast. There is also a new class library project that uses the project.json and the roslyn compiler, building actually produces nuget packages rather than dll files in the bin. There is no bin folder beneath the web app anymore. Even the core framework itself is composed of nuget packages.

Things that used to implemented as httpmodules would now be implemented as OWIN Middleware components.

Like you I've always been reluctant to install any preview version of VS on my main machine, but I did install the new RC recently because from watching the asp.net community standup videos on Scott Hanselman's youtube channel I learned that it is ok to install it, it will run side by side with VS 2013 and when a new or final version comes out you can just install on top of the old one without uninstalling. Even the download page for VS 2015 RC has no warning about installing it on your machine as they did have in the very early previews where they suggested using a vm.

I'm not sure I know enough about the internals of MVCSiteMapProvider to help and at the moment I have a lot to do converting the code for my own app. But when I get that done and I need to get menus working I will probably at least have to take a look and see if I can be of help in order to move my own app forward.

@NightOwl888
Copy link
Collaborator

I agree that it will be best to use the cross-platform .NET core, and not have any dependencies on System.Web. And since MvcSiteMapProvider is a web-only framework, I don't think that will be a problem.

Ideally, if possible, we would like to continue using a single code base for all MVC versions, including MVC 6. It sounds as if we must switch to using config.json in order to support .net framework core. If I am not mistaken, we will also need the .csproj file for supporting older versions of Visual Studio (or will there be some updates so Visual Studio 2012 and 2013 can support project.json?). It would be nice if we could sync the code files between the two project files so adding a file using one environment adds the file in the other environment automatically. Or perhaps it would be best to start with a .csproj to maintain compatibility with the rest of the MVC versions and have the build script generate the project.json file, the .net framework core DLL, and NuGet package.

Or perhaps we could do it the other way around. It sounds like Roslyn will be able to build projects for older .NET framework versions, so we might be able to swap out msbuild for Roslyn across the board for the build.

The whole point of moving to DI in the first place was to conform to a set of interfaces that know nothing of the underlying implementation. Technically, MvcSiteMapProvider doesn't depend on System.Web, it primarily only depends on some of the abstractions provided by System.Web, System.Web.Routing and System.Web.Mvc. So, the simplest course of action to port MvcSiteMapProvider to MVC6 may be to create adapters around the new interfaces that are based on the old interfaces (and re-implement the old interfaces locally inside of #if MVC6 symbols, copying them from the old libraries).

For example, whenever HttpContextBase is passed through an interface in MvcSiteMapProvider, a custom implementation could be passed that wraps the relevant context object from ASP.NET 5 and implements the current HttpContextBase abstract class (copied into the MvcSiteMapProvider code base). The advantage of this approach is that the core interfaces of MvcSiteMapProvider will remain unchanged, which in turn means less maintenance, and less likelihood to introduce bugs as part of the port. Ideally, we can add the MVC 6 support to the project without affecting any of the other MVC versions or making any changes to any existing interfaces (in other words, just make it a patch to the existing version).

One trick will be to find out how to add conditions to project.json the same way they can be added to .csproj, as we won't be able to support conditional dependencies (i.e. versions past MVC 6) without them.

There will also likely need to be some new components (or just conditional sections of the existing components) to operate with the new Authorization functionality, resolving controller types, resolving URLs, etc. Unfortunately, the interaction with Routing has not been put into services, it is part of the SiteMap itself, so there will likely need to be some changes to interact with Routing in ASP.NET 5. But those could simply be put into conditional compilation blocks.

DI

As for using the built-in DI in ASP.NET 5, I respectfully disagree. Mark Seemann did not agree that the approach Microsoft took was the correct one (at least not very early on). There are others (who I respect) who agree that Microsoft took the wrong approach. As a maintainer of this project I don't think it would be wise to depend on ASP.NET any more than is necessary, including adding a dependency to an external DI framework that many experts agree is overly complex.

Unfortunately, there was a chapter missing from Mark Seemann's book. The book was all about configuring applications, but the advice does not blanketly apply to libraries or frameworks, which were posts that he released after MvcSiteMapProvider v4 was complete. The direction we are heading is to eventually phase out the internal/external DI container (which is essentially a conforming container) using the approaches described in those articles. This means that whatever DI container comes in the box with ASP.NET is irrelevant and unnecessary as far as we are concerned.

The plan is to allow you to optionally reconfigure the MvcSiteMapProvider DI pipeline at application startup, allowing swapping-out of individual dependencies. This will mean you will simply be able to use the new keyword to inject your own dependencies (similar to how you inject IDependencyResolver in MVC 5), as there are still a large number of developers who would like an alternative to using full-on DI for more complicated configurations. There will be a unified fluent configuration API (consisting of several fluent builders and abstract factories) where the entirety of MvcSiteMapProvider can be configured (configuration options, nodes (hierarchical nodes in code!), code-based node providers, multiple SiteMaps, mapping of requests to SiteMaps, etc.). For backward compatibility, all existing configuration options will still be supported, but we will likely discourage using external DI after the fluent API is in place, and phase it out in a future major version. The emphasis will still be for MvcSiteMapProvider to "just work" when installed, but also allow for replacement of individual pieces with or without a DI container.

What is gained? Developers won't have to worry about changes to MvcSiteMapProvider's internal configuration breaking their external DI configuration when they upgrade MvcSiteMapProvider. Developers who don't want to use DI for advanced options don't have to. All configuration of MvcSiteMapProvider can be done in one place using fluent code constructs that are easy to read and understand. Creating multiple SiteMaps will be a quick and easy procedure. Going forward, MvcSiteMapProvider won't depend on web.config. We won't need to maintain any external DI packages or configurations for specific DI containers, or provide documentation for same. It will be much easier for us to add features because we won't have to consider the external DI configurations. So this is a win-win.

Workflow

There is currently no documentation for this, so this is intended to be a primer to understand how MvcSiteMapProvider works, along with what parts of core ASP.NET/MVC it uses.

Like nearly any web technology, MvcSiteMapProvider is driven entirely by the incoming requests. The requests begin at the point where the HTML helpers or the /sitemap.xml endpoint are called. If more than one HTML helper is on the page, there will be as many requests to MvcSiteMapProvider. However, the SiteMap object is stored in a shared cache (which can plug into either System.Web.Caching or System.Runtime.Caching), and it also extensively uses request caching through System.Web.HttpContext.Items to keep data alive between HTML helpers.

  1. On the first request, the cache is loaded using the ISiteMapBuilder, ISiteMapNodeProvider, and IDynamicNodeProvider services. During this stage, there is a different request-cache pool than that used in production to prevent the build stage from polluting the current request. In general, these services do not depend on ASP.NET (except for some enumerations). This is done only when the SiteMap cache expires.
  2. The URLs for the nodes are resolved using a fake HttpContext object based on the home page (to prevent the injection of "ambient values" into the URL - see parameters for current request injected into nodes where the action has the same parameter name #213). Most of the time the URLs are not cached, but they are cached along with the SiteMap when there are no custom route values and no preserved route parameters. This phase uses the UrlHelperAdapter (based on an internal IUrlHelper interface since MVC didn't provide one), and HttpContext (both internally and through the UrlPath service, which is a duplicate of some of the ASP.NET functionality and may need rework).
  3. Both the URL and the route of the incoming request are compared with each node until a match is found (the "current" node). This is done primarily within the SiteMap, the SiteMapNode, and the RouteValueDictionary. This stage depends on the URL information in the HttpContext object as well as System.Web.Routing.
  4. Before comparing each node, any route keys that are configured as PreservedRouteParameters are copied from the URL or Route (route takes precedence) into the request-cached RouteValueDictionary. These values are discarded after the current request ends. This stage depends on the URL information in HttpContext object, System.Web.Routing, and System.Web.HttpContext.Items dictionary.
  5. Visibility for each node (requested by the HTML helpers) is determined. It is done using both an internal ISiteMapNodeVisibilityProvider interface and a IAclModule interface. This phase is not cached. The visibility provider does not depend on ASP.NET, but the AuthorizeAttributeAclModule depends on several interfaces including the AuthorizeAttribute and Controller, ControllerContext, andHttpContext`.
  6. From the Controller (or anywhere else) it is possible to override several of the configured node values with temporary values per request, which is how the [SiteMapTitleAttribute] works.

There is also another slightly different workflow when calling the /sitemap.xml endpoint which is pretty much the same except for it outputs XML through an internal Controller. The Controller would probably be better suited for OWIN middleware in ASP.NET 5.

In general, the classes in the Web namespace are most dependent on ASP.NET and MVC directly, and many of those will need rework, including the ControllerTypeResolver, which will likely need a new implementation for MVC 6.

XML Sitemap

Do note that there is a new XML Sitemap implementation in the works that I have recently put into a production project for testing. I am considering making this into a separate NuGet package (so we can support ASP.NET 2.x to 5.x), but there are many common dependencies and there will need to be some configuration to make it interact with MvcSiteMapProvider. See #345.

Ideally, that piece should be ported to MVC 6 as well (although the fluent API is not yet complete).

Tag Helpers vs HTML Helpers

It would be interesting to also support Tag Helpers as an alternative to using HTML helpers. This may end up being a big win since the templated HTML helpers are not so obvious to use (the templates are tucked away in the /Views/Shared/DisplayTemplates folder).

It would be better if the developer could simply use a tag for the Menu and then provide their own CSS class using an attribute and other menu customizations using nested tags that represent repeating sections similar to how ASP.NET templated controls work (if that is possible).

At this point, these are not strictly necessary because HTML helpers are still supported but they are an interesting alternative nonetheless.

MvcSiteMapProvider vNext

I still think that MVC 6 support can be added without a major release, but in case that isn't true, here are some of the major issues that are awaiting the next release.

  1. Fluent configuration API (I already mentioned). I decided to use an approach that prevents the developer from being able to use conflicting configuration (enforced by the compiler). Unfortunately, this solution is a maintenance nightmare, so I decided to also create a Visual Studio plugin to automate the process of creating Fluent API business rules (which I plan to sell), and that project is currently on hold.
  2. SiteMap indexing support (for Reducing load of generating sitemap #258). Ideally, it would be possible to load the SiteMap from streams (the underlying file system) so it could potentially host millions of nodes. My thought was to reverse engineer Lucene.Net to mimic what their indexing feature does. Ideally, we would be able to store indexes in memory or on disk, do partial updates to them (based on individual ISiteMapNodeProvider instances), and to do route matching against an optimized index that streams through the data instead of keeping it all in memory.
  3. New XML sitemap functionality (I already mentioned), as a new GitHub project and new package that has a shared dependency with MvcSiteMapProvider and will interact with MvcSiteMapProvider when present. See Requirements for New XML sitemap Endpoint #345.
  4. New SiteMap localization endpoint to support resources in external libraries. See Requirements for New Localization Extension Point #344.
  5. Refactor the node-matching functionality into injectable services. Some people have expressed the desire to match nodes based on session state and other parts of context and it would be nice if there was a way to do so without replacing the SiteMap or SiteMapNode implementations.
  6. Factor the URL resolving into separate services for URL-based vs Route-based resolution, and fix the SiteMap so it understands which resolver is being used. See Add option to interpret a result of custom url resolver as MVC url #392.
  7. Create a new business rules system that that the end user can customize, rather than hard coding rules in the SiteMap. My thought is that it should be similar to the rules system in CSLA, but simpliified and DI friendly.
  8. The overloads of the HTML helpers should be reviewed and excess removed/deprecated. There should also be a configuration object that is populated with the defaults in order to make the logic within each overload simpler and ensure the same defaults are used throughout the HTML helper API.

In addition to the remaining open issues (which mostly can be closed with the above work), there is another list of issues that need to be addressed (some of which have been addressed in the above list).

Needless to say, if we don't break the interface of the current version, the above work can be avoided for the port of MVC 6.

@joeaudette
Copy link
Author

Thanks for all the great information.

I want to mention about DI, Mark Seeman's book came out in 2012 and was very influential. However, before you dismiss the DI from Microsoft please note that it is not one of the older ones criticized by Mark Seeman, it is brand new in the Microsoft.Framework.DependencyInjection namespace and I suspect the fact that they decided to start over and not use one of the older ones is probably because of the influence of this book. Keep in mind that anyone who starts a new MVC 6 project is going to get this one by default unless they change it themselves and probably most people won't change it unless they just really love some other DI for some reason. This one looks very good to me.

Another concept that is new and may be very useful in terms of replicating needed interfaces without taking a binary dependency on assemblies you want to avoid, is the concept of "assembly neutral interfaces". You can find some information about this by google if you have not seen ti already.

@joeaudette
Copy link
Author

oh, sorry, I spoke out of turn, I had not read the forum post you linked, I see that it is the new DI that Mark Seemann did not agree with not just the old ones.

@NightOwl888
Copy link
Collaborator

Update

I have been doing research on this subject and have made some headway. However, after spending 2 days attempting to reuse the current code base and make a direct port to MVC 6, I can see there are quite a few incompatibilities.

Also, I have been searching for a way to actually make reuse of the conditional dependencies of MVC going forward without any luck (I even offered a 50 point bounty, but still nobody is answering). I did come up with a (not so good) solution, though - using globs to select source code to compile outside of the build directory.

  "compile": "../MvcSiteMapProvider/**/*.cs",

  "compilationOptions": {
    "define": ["MVC6", "NET46"]
  },

Then you can structure the directories like

MvcSiteMapProvider/
MvcSiteMapProvider.MVC6/
MvcSiteMapProvider.MVC7/

and each MVCx directory can have its own project.json file with references to the same source. It works, but Visual Studio doesn't display any of the code files in the MVCx directory. I (sort of) solved that by adding a .projx and project.json file to the MvcSiteMapProvider folder.

Anyway, it occurred to me that there are 4 different options for moving forward:

  1. Do a straight port to MVC6, copying any missing types from the current MVC stack and wrapping the new ones without changing the MVC2-5 functionality.
  2. Create a completely new library to target MVC6, and let MvcSiteMapProvider die with MVC5.
  3. Split the library into several pieces and pare it down to its bare elements, and then have a set of integration libraries for each version of MVC.
  4. Do both 1 and 2 so users of MvcSiteMapProvider have a way to upgrade, but create a new package for the best integration with MVC 6.

In all cases, it would probably be best to exclude the XML sitemap functionality (for search engines) from MvcSiteMapProvider for the ASP.NET 5 stack and make it a separate package using the prototype I made previously.

I am also inclined to push the Fluent API forward for the integration, since it makes total sense to add to the existing configuration scheme that Microsoft started (for both OWIN and for MVC6).

            // Add MVC services to the services container.
            services.AddMvc();

            // Add MvcSiteMapProvider services to the services container.
            services.AddMvcSiteMap();

Straight Port

Pros:

  1. Fastest path to integrating with MVC6.
  2. The same features/bug fixes can be applied to all versions of MVC (that support them) going forward.
  3. The fluent API can hide most of the dirty details of API mismatching.

Cons:

  1. Won't take advantage of many of the new features of MVC6.
  2. Many things that need a redesign won't be addressed yet.
  3. Only about 50% of the code is reusable as is - there will be a lot of patching and conditional compilation symbols to pull this off.
  4. The code will be difficult to maintain, since there will be a lot of #if MVC6 sections in the code.

Start Over

This could be the opportunity to fix the project name, which is just wrong.

  1. This is no longer a "provider", so we can drop that from the name.
  2. "Site map" has come to mean other things (a web page, and an XML format) since Microsoft came up with this in ASP.NET 2.0. It would be better if the package indicated that it is for site navigation.

I think we should contact Microsoft to see if they are open to creating a Microsoft.AspNet.Navigation package first (or perhaps just adding the Navigation namespace into the Mvc project), and if they are not, we can still name the package AspNet.Navigation. It would be nice if there were official support from them, though.

Pros:

  1. We can take advantage of the new MVC features, since we won't need legacy support.
  2. Many design issues can be addressed (Passive Attributes, Localization (Requirements for New Localization Extension Point #344), XML Sitemap (Requirements for New XML sitemap Endpoint #345), conforming DI container, URL resolving (Add option to interpret a result of custom url resolver as MVC url #392), remove obsolete features, etc.)
  3. Set more reasonable defaults for MVC6 (no automatic startup, no XML by default, no web.config settings, turn off localization, etc.).
  4. Easier to maintain, since legacy MVC support won't be required.

Cons:

  1. More effort required to implement.
  2. Features and bug fixes won't be shared on the legacy MVC stack.

Divide and Conquer

Pros:

  1. Features and bug fixes can be shared between MVC stacks in many cases.
  2. Having a piece that implements the core SiteMap functionality that is separate from MVC will make it better insulated against design changes to ASP.NET in the future.

Cons:

  1. Having to support 2 different stacks with the same set of libraries could be very challenging.
  2. There would need to be an upgrade path to anyone who is currently using external DI, which means a lot of packages to support (and an even bigger dependency chain than we have now).

Port and then Redesign

Pros:

  1. We can get the port out in a hurry, then take our time to create a better design that fits well with the new stack.
  2. There will be an upgrade path for current users.
  3. Features and bug fixes can be maintained in the port.

Cons:

  1. Even more effort than 1, 2, or 3 individually.

@joeaudette
Copy link
Author

I can tell you that since asking about this I have gone ahead and implemented something new for navigation that is almost feature complete for my needs but does not have every feature that your project provides. I do have menus and breadcrumbs working and my current implementation supports use of either xml or json for building the navigation tree from a file. I still plan on adding a way that a navigationnode in the xml or json can point to another INavigationTreeBuilder so that for example a cms could implement one that builds its sub tree from a database building on top of the one that is built from xml or json. I plan to move this code to its own public repository and make it open source under apache 2 soon after MS ships asp.net 5 beta8. In beta8 we will be able to package views and viewcomponent templates and other static files in the nuget created by building a class library project. So at the point my main project can just take a nuget dependency on my navigation library. That is why I'm waiting till beta8 to move the navigation project out of my main repo. So at the moment my work is in a private repo.

So as far as the start over option my project may interest you and I would welcome collaboration on it if it does interest you. I implemented the menu and breadcrumbs as a viewcomponent and I also have a taghelper for pagination that will be in the same project. I still have some work to do as far as caching the navigation tree once it is built, but that should not be difficult work.

@NightOwl888
Copy link
Collaborator

Sure, I'd like to see what you came up with.

Although based on what I am seeing in the AspNet project, it might make the most sense to make navigation into middleware (since that is where session and routing are).

Also, since a contributor offered up a glimpse of a fluent API, that seems like the most logical way to configure navigation (since writing the configuration will happen few times, and reading it again will happen many). The new startup section of MVC makes this even more compelling. This enables us to completely get rid of the internal vs external DI mess and provides a unified way to configure the entire library. Seems way better than trying to manage configuration files, but that could always be a another option (along with .NET attributes and custom node providers).

As for caching, one of the things I have learned on this journey is that making the best use of memory really pays off. MvcSiteMapProvider has a limit of about 10-15 K nodes before it really starts to bog down the system with disk swapping. So my thought was to try to split up the "indexing" data (that is, the route values/url) from the rest of the data on the node, and then lazy load the rest of the data as the UI requests it into separate caches, so nodes' data that is rarely accessed can be removed from memory. It wouldn't be as good as reverse-engineering Lucene.Net, but it would probably increase the amount of nodes you could deal with considerably.

Also, taking a page out of the MVC project, it occurred to me that the node matching should be made asynchronous and perhaps optimized in other ways too (like searching by action name first, then searching the subset for the matching node). The Decision Tree namespace also has some interesting ideas in it.

@joeaudette
Copy link
Author

I'll post a link when I make it public. You may not like my version or my vision for it. To me it is a UI component and nothing related to middleware and I have tried to keep it as simple as possible, you may find it too limited. I will welcome feedback both positive and negative once you do see it.

@NightOwl888
Copy link
Collaborator

Simpler is usually better. That is what I am liking about the new MVC - gone are the days of the 1000 line+ class definition. Maybe you are right and I have been looking at it all wrong the whole time - it should just be a simple UI component. I am curious to know if you found a simpler approach to node configuration, as well.

It seems like there must be a way to piggy-back on the ActionSelector to pick the right node. Food for thought...

@joeaudette
Copy link
Author

If you'd like to have an early look I'm happy to add you temporarily to my project. I know you are a reputable guy and I trust you not to do anything wrong. The other stuff in my solution will also be open source later on anyway. Just don't be too harsh on me with criticism since it is an early view. for example I know I currently have some logic in my my NavigationViewModel that needs to be factored out of there. Let me know and I'll add you, I can also give you easy directions to get the solution working so you could actually see the navigation. Really just a matter of giving it a connection string.

@NightOwl888
Copy link
Collaborator

Sure, I would appreciate it. You don't have to give me write access or anything, but yea, I would like to see how it works.

@joeaudette
Copy link
Author

Since the repo is currently private the only way I can let you in is by allowing you write access. I've let you in and I just updated the file DeveloperQuickStart.txt in the notes folder. It has instructions for getting it working.

@waynebrantley
Copy link

My 2cents:
I use this as a display component only. When the user is on a page, I want to know what node they are on so I can display the correct menu. Currently what I use is very simple - I have all top level nodes on tabs at the top and when you click one of those tabs, the nodes under that selected one appear on a side menu on the left. I use visibility selector - so I can have nodes that are not on a menu, yet keep the tab at the top highlighted. I use the security trimming feature so only things they have rights to are shown.

I use autofac for DI. Autofac integration will be released with vNext directly. It is 'included in the box'. I would support that everything in your library REQUIRES DI and there is no built in mechanism. It uses this Autofac out of the box in MVC6 and if users want, they can replace it.

I have been using this library for many years and that is all I use. Hardly seems like a middleware type of thing. @joeaudette I probably could fit in what you built?

btw, I think a clean break 'Start Over' is what looks the best route to me.

@waynebrantley
Copy link

@joeaudette Any chance I could see your work and see if that is the direction I would like to go toward?

@joeaudette
Copy link
Author

Sure it is public now, https://github.com/joeaudette/cloudscribe.Web.Navigation

@kfrancis
Copy link

kfrancis commented Jul 5, 2016

Any news here?

@hidegh
Copy link

hidegh commented Jul 6, 2016

Hi, is there any progress on MVC6 sitemapprovider? Asp.net core 1.0 is out...the MVC6 branch is running for a while...is there any planned release date with the same features as MVC5?

Btw. focusing purely on the sitemap funcions, is it possible to have a single code-base and a interface with 2 implementatin - with MVC5 related System.Web helpers and with the same helpers and MVC6 implementation?

@kfrancis
Copy link

kfrancis commented Jul 6, 2016

I've been trying to work on "Port and then Redesign", to try and help since I really don't want to look at another library for menu systems, but I can't even get it to build. :(

administrator_ windows powershell 2016-07-06 13 13 04

@maartenba
Copy link
Owner

Port will be hard, as all underlying bits have changed... Time, need time :-)

(the compilation error may be because MVC2, which we still support, is missing on your system)

@ToddThomson
Copy link

@maartenba I too am porting my MVC sites to ASP.NET Core MVC. My solution to porting SiteMaps was to use Options (configuring POCO settings ) and to setup the site navigation in the appsetting.json file. Essentially you have navigation collections with nested nodes ( which are similar to a SiteMapNode ).

Here is what the Navigation section looks like:

"Navigation": {
    "Collections": [
        {
            "Name": "Site",
            "Description": "Site Menu Collection",
            "Nodes": [
                {
                    "Title": "Home",
                    "Url": "~/",
                },
                {
                    "Collection": "Admin"
                },
                {
                    "Title": "TopMenu1",
                    "Nodes": [
                        {
                            "Title": "SubHomeA",
                            "Url": "~/subhomeA"
                        },
                        {
                            "Title": "SubHomeB",
                            "Url": "~/subhomeB",
                            "Roles": [ "Administrator", "SuperUser" ]
                        }
                    ]
                },
                {
                    "Title": "DynamicTopMenu",
                    "Key": "/Site/TopMenu"
                }
            ]
        },
        {
            "Name": "Admin",
            "Description": "Administration Menu Collection",
            "Nodes": [
                {
                    "Title": "Admin",
                    "Roles": [ "Administrator" ],
                    "Description": "Site Administration Menu",
                    "Nodes": [
                        {
                            "Key": "/admin/content",
                            "Title": "Content Management",
                            "Description": "Content Type Management",
                            "Nodes": [
                                {
                                    "Title": "Articles",
                                    "Url": "~/articles/admin/list"
                                }
                            ]
                        }
                    ]
                }
            ]
        }
    ]
}

@NightOwl888
Copy link
Collaborator

NightOwl888 commented Jul 15, 2016

Just wanted to post my thoughts here. Lots of thoughts...no time to implement them :). In case someone else gets the jump on this work, here is the direction I would probably go (as it makes the whole thing better and easier to implement at the same time). But feel free to chime in if you disagree or can improve on these ideas.

MvcSiteMapProvider vNext Revisited

When I took a stab at attempting to make a port, it didn't seem very likely that we can make it work without a major version change. So, it might be better to Divide and Conquer in conjunction with a redesign. Basically, split everything up first in an effort to release a completely new version, and factor all commonality out so we can reuse it in both stacks. However, I am still thinking that a name change to AspNet.Navigation is in order (at least for the MVC Core stack).

Yea, I know, it means everyone has to throw out their existing code, and reimplement their business logic in order to upgrade. But my thought is to eliminate the need for most of that code, so you will be deleting a lot more code than you need to add.

So, perhaps the DLL project structure could be:

         MvcSiteMapProvider.dll             AspNet.Navigation.dll
                            \                  /                         
                           AspNet.Navigation.Core.dll 
(Namespace conditionally compiled as MvcSiteMapProvider or AspNet.Navigation)

Of course, we could either ILMerge them together into a single DLL for each stack, or simply put both the main DLL and Core inside the same NuGet package.

More MVC, Less Provider

After coming across Mark Seemann's Provider is not a pattern post some time ago, I realized that the "providers" in MvcSiteMapProvider are not actually "providers". So moving away from this naming convention is a good thing.

I don't think anyone will disagree with my view that MvcSiteMapProvider requires way too much configuration code. There have been a HUGE number of questions about configuring route values and preserved route parameters, generating URLs, confusion about how to dynamically change the SiteMapPath, how to load nodes from dynamic sources, and how to control node visibility. So my thought is to embrace the MVC pattern, and move away from the ASP.NET 2.0 provider model that much of this is based upon. Here are some of the design changes that I think will make for a much more intuitive experience.

  1. Use RouteData.DataTokens as one option for providing key/parent key relationships directly in the routing configuration. Some convention-based settings such as the pattern used for routing ({controller=Home}/{action=Index} - but we need to invent ours...my thought is to make them similar to namespaces sort of like what MvcCodeRouting does to maintain their hierarchy of controllers) should also be used to set the name of the key of the current route combination and the name of the key of the parent. Due to the fact that routes don't map 1-1 with actions, this might be a bit of a challenge, so ideas are welcome.
    1. There should be something similar to route constraints that can be added to the configuration to do complex mapping that the conventions don't support by running a custom class.
    2. There should be extension methods in each stack that are similar to the ones in the box that allow configuration of these "extra" values in the route configuration.
    3. Provide a way for the project to be scanned via Reflection to automatically load all controller action methods as nodes and map them using the DataToken parent key information, if present (which can be overridden by .NET attributes on the controllers and actions). Essentially, MvcSiteMapNode will no longer exist - we will just provide various attributes (which filters run) that control the loading process.
  2. Make a statically cached navigation tree (Model) that can be based on configuration (XML, JSON, .NET Attributes, or RouteData.DataTokens by convention). This will essentially be the part that is visible in the main menu.
  3. Make static nodes match on area, controller, action, and HttpVerb only. That is the way MVC does it. That is the way we should do it. We only match the static nodes in the tree, which will generally be 1-1 to action methods. This will eliminate scalability problems we currently have and also simplify unique combination checking (lots of people have went against my advice and put duplicate nodes in the SiteMap, even though I insist this is not a supported configuration - an error would help). Exception: Still need to support multiple navigation paths to a page. There will need to be a way to add to the match in this case (perhaps by using another route value or by using RouteData.DataTokens).
  4. Use Filters, just like MVC. The filters can either be an extension of the MVC filter framework (if it is extensible), or worst case just make a wrapper MVC filter that executes our filter framework. Make these filters have 2 parts just like MVC filters 1). A class that decides if the filter should run in the current context (similar to an MVC filter) 2). A class (possibly derived from ActionResult) that is executed when the filter returns that does the real work (in MVC we set filterContext.Result to a non-null value, which indicates whether it should run).
    1. Rather than having Dynamic Node Providers, make Navigation Filters. @joeaudette came up with a good idea to separate the "model" hierarchy from the "view model" in his implementation. I have to also give @maartenba credit as it is similar to the visitor pattern idea he came up with. But my thought is to make a separate filter for each navigation component (that would be HTML helper in the current stack, view component in the new stack) and make a separate Type of Filter for each (include the ability to have named instances of navigation components). Multiple ordered filters can be applied on each navigation component, and they can change the View Model (which starts as a copy/partial copy of the static model) of the navigation component in an object oriented way, similar to the example on the SiteMap.SiteMapResolve Event MSDN page. This gives the user the ability to change the breadcrumb trail and menu in a way that is meaningful for the current context they are working in, which solves Caching per-user #16. The user will be able to dynamically set the current node in the filter result. Also, there should be some sort of configurable output caching mechanism that stores the result so it can be reused on subsequent requests, rather than hitting the database over and over on each request if there is a database call in the filter.
    2. Rather than having Visibility Providers, make Node Visibility Filters, and make the Security Trimming into a specialized Node Visibility Filter. The user can intervene by putting a filter before the Security Trimming filter, which addresses How to explore all nodes regardless the security trimming ? #102. Basically, the view model should have an IsVisible property that can be set as needed (possibly made invisible in other filters by passing an interface that doesn't include it, so only visibility filters can interact with it - SRP).
    3. There should be a simple way to check whether the user is authorized within the context of all filters by passing in a specific node of the view model.
    4. Remove preserved route parameters. They will not be considered for node matching. MVC automatically preserves route values from the current request (see parameters for current request injected into nodes where the action has the same parameter name #213). What we really need is a way to exclude them. For that, there should be a URL Resolution Filter so the context that is passed to the URL resolver can be modified to generate the correct URL. Perhaps there can be a built-in filter and a property for Ignored Route Parameters or something along those lines. Also, we should allow the user to intervene and change the URL before it is populated in the View Model (similar to how MVC action filters allow changes before and after the action method).
    5. The SiteMapTitleAttribute (filter) can just be eliminated. The user can use a Navigation Filter (in a method executed after the action is executed, similar to action filters) to read the title from the current MVC View Model and/or the ViewData (or do a separate query) and set it on the appropriate nodes. Perhaps there still could be a separate attribute to map node key to the data id and to a specific node property so you can set any property of the navigation view model to a specific value in the context of the view model/view data of MVC, but limiting it to title seems...limited.
    6. The SiteMapCacheReleaseAttribute can still be included, but should be split into separate action filter and attribute classes. However, it will likely be rare to release the cache if the user can just rewrite the node hierarchy view model of each navigation component dynamically.
    7. After the navigation hierarchy is populated, have a series of Validation Filters run to ensure its state conforms with the validation rules (no duplicates, etc.). If these are user extensible, additional filters can be made by end users on custom attributes.
    8. In the box, there should be a filter that covers making Dynamic Nodes that uses attributes to configure them. Dynamic Nodes should be able to be populated by data from the view model/ViewData much like the way SiteMapTitleAttribute works now. The simplest scenario is just to add another level on the fly (for CRUD methods for example), but there should also be a convention-based way to make 2 or 3 levels, including grouping nodes, as well. For more advanced scenarios, a custom filter will be required.
  5. Rather than ISiteMapNodeProvider, make convention-based Navigation Controllers that will execute automatically when put into a known location (possibly look into extending the view engine locations) to load static navigation nodes. The navigation controllers can return a result (similar to or inherited from ActionResult) that executes a specific type of action. Examples could be XmlFileResult("thefile.name"), JsonFileResult("thefile.name"), ControllerActionScanResult(), AttributeScanResult<attributeType>(), etc. plus custom result types. The controllers can be decorated with filter attributes to control which SiteMap instance(s) will be populated with the nodes. Basically, the built-in functionality will use Navigation Controllers and you can add custom Navigation Controllers/turn off the built-in Navigation Controllers to customize.
  6. Break MvcSiteMapNodeAttribute into individual attributes for various properties (especially for custom attributes, since dictionaries are not directly supported by .NET attributes).
  7. Fix lifetime management issues so that extension points with disposable resources can clean up at the end of the request. This will likely need to be an MVC global filter that calls into AspNet.Navigation to tell it to release at the end of the request.
  8. Use a separate data type to make non-clickable grouping nodes than clickable nodes.
  9. Separate node functionality into common interface buckets (routing, canonical, meta robots, visibility, display, etc.) so facades of the node can be passed into filters, eliminating the non-relevant members.

In essence, this means that the current SiteMap structure can be made into a very simple Navigation Model that the user doesn't have direct access to, except perhaps in the Navigation Controllers. We could go on using something similar to our current cache without running into scalability problems because it will not usually contain more nodes than there are action methods in the project. The request caching part (and the confusion that comes with it) can be eliminated. All of the internal IsAccessibleToUser code and references to HttpContext, ControllerContext, and RequestContext can be eliminated. Most of the FindNode methods can be made into extension methods that are specific to an MVC stack, just leaving a simple one that passes in a facade interface that can be used to wrap the RouteData in each stack's extension methods. Nearly all of the internal things that SiteMap and SiteMapNode now do can be put into external filters, making them nothing more than "dumb" models with some extension methods to do some of the more complex stuff.

All of this means we will be able to eliminate some of the thoughts from my original comment and prior the list of issues about vNext. So as you can see below, this actually eliminates some work (especially on those overly complicated file streaming (no. 2), business rules (no. 7), and cache chunking (no. 24 from the list of issues) ideas that I had dreamt up previously).

  1. Fluent configuration API (I already mentioned). I decided to use an approach that prevents the developer from being able to use conflicting configuration (enforced by the compiler). Unfortunately, this solution is a maintenance nightmare, so I decided to also create a Visual Studio plugin to automate the process of creating Fluent API business rules (which I plan to sell), and that project is currently on hold.
  2. SiteMap indexing support (for Reducing load of generating sitemap #258). Ideally, it would be possible to load the SiteMap from streams (the underlying file system) so it could potentially host millions of nodes. My thought was to reverse engineer Lucene.Net to mimic what their indexing feature does. Ideally, we would be able to store indexes in memory or on disk, do partial updates to them (based on individual ISiteMapNodeProvider instances), and to do route matching against an optimized index that streams through the data instead of keeping it all in memory.
  3. New XML sitemap functionality (I already mentioned), as a new GitHub project and new package that has a shared dependency with MvcSiteMapProvider and will interact with MvcSiteMapProvider when present. See Requirements for New XML sitemap Endpoint #345.
  4. New SiteMap localization endpoint to support resources in external libraries. See Requirements for New Localization Extension Point #344.
  5. Refactor the node-matching functionality into injectable services. Some people have expressed the desire to match nodes based on session state and other parts of context and it would be nice if there was a way to do so without replacing the SiteMap or SiteMapNode implementations.
  6. Factor the URL resolving into separate services for URL-based vs Route-based resolution, and fix the SiteMap so it understands which resolver is being used. See Add option to interpret a result of custom url resolver as MVC url #392.
  7. Create a new business rules system that that the end user can customize, rather than hard coding rules in the SiteMap. My thought is that it should be similar to the rules system in CSLA, but simplified and DI friendly.
  8. The overloads of the HTML helpers should be reviewed and excess removed/deprecated. There should also be a configuration object that is populated with the defaults in order to make the logic within each overload simpler and ensure the same defaults are used throughout the HTML helper API. EDIT: My current thinking is to make a fluent API for the current stack similar to FluentBootstrap so we can eliminate the ridiculous number of overloads. For MVC Core, I think we should go with view components instead of HTML helpers.

In addition to the remaining open issues (which mostly can be closed with the above work), there is another list of issues that need to be addressed (some of which have been addressed in the above lists).

@hidegh
Copy link

hidegh commented Nov 30, 2016

I think nobody would mind that he had to use old partial views inside MVC6, if it'd mean that a new release would roll out for it - just to make MvcSiteMapProvider usable on MVC6 in the first place (so basically "just" switching to new interfaces while getting controllers/actions). Other enchancement could wait...

@joeaudette
Copy link
Author

@ALL @hidegh just fyi, if you need something that works today for asp.net core, I have a navigation/sitemap solution that is working well in my projects and lots of other people using it too
https://github.com/joeaudette/cloudscribe.Web.Navigation

@kfrancis
Copy link

Any head way? I don't even want to contemplate Asp.Net Core until there's a solution for this library. I just love this library too damn much and I've put a ton of work into the integration with my system.

@maartenba
Copy link
Owner

Unfortunately none at this time :-/

@hidegh
Copy link

hidegh commented Nov 7, 2017

How about a more generic .NET standard support?

Also wondering what effect might bring splitting rendering code (partial views / components) from the "Core" functions...

@toddca
Copy link

toddca commented Dec 3, 2018

Anything? Does anyone know of a good alternative to this library? I don't require anything super complicated.

@joeaudette
Copy link
Author

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Projects
None yet
Development

No branches or pull requests

8 participants