Skip to content
This repository was archived by the owner on Dec 14, 2018. It is now read-only.

View Components in a separate assembly #3750

Closed
johnnyoshika opened this issue Dec 13, 2015 · 57 comments
Closed

View Components in a separate assembly #3750

johnnyoshika opened this issue Dec 13, 2015 · 57 comments
Labels
Milestone

Comments

@johnnyoshika
Copy link

I'm trying to get view components to load from a separate assembly. I posted a question on how to do this on StackOverflow and it doesn't seem like anyone knows how to do this. Any chance someone can weigh in on the subject here?

http://stackoverflow.com/q/34236850/188740

Thanks!

@pranavkm
Copy link
Contributor

  1. Embed the Razor views in the ViewComponent assembly
  2. At startup, replace RazorViewEngineOptions.FileProvider with a CompositeFileProvider
services.Configure<RazorViewEngineOptions>(options =>
{
    var embeddedFileProvider = new EmbeddedFileProvider( typeof(BookOfTheMonkViewComponent).GetTypeInfo().Assembly);
    var compositeFileProvider = new CompositeFileProvider(
        embeddedFileProvider, 
        options.FileProvider);
    options.FileProvider = compositeFileProvider;
}

I haven't actually tested this out, so let me know if it fails in some awful way.

@d0pare
Copy link

d0pare commented Dec 14, 2015

@pranavkm where CompositeFileProvider is defined ? Does it included in RC1?

@johnnyoshika
Copy link
Author

I can't find CompositeFileProvider either. There seems to be a test implementation example of IFileProvider here, but no real CompositeFileProvider class:

https://github.com/aspnet/Mvc/blob/dev/test/Microsoft.AspNet.Mvc.TestCommon/TestFileProvider.cs

@pranavkm
Copy link
Contributor

The changes went in farely recently so you'd have to point to the aspnetvnext feeds to pick up these packages. https://github.com/aspnet/FileSystem/tree/dev/src/Microsoft.AspNet.FileProviders.Composite

@johnnyoshika
Copy link
Author

Thanks you @pranavkm!

CompositeFileProvider requires a collection of IFileProviders. In your example, you're passing an assembly to the CompositeFileProvider constructor. How can I get an IFileProvider from the assembly so that I can pass it to CompositeFileProvider?

Thanks,
Johnny

@pranavkm
Copy link
Contributor

My bad - corrected my code snippet. It should be
var embeddedFileProvider = new EmbeddedFileProvider( typeof(BookOfTheMonkViewComponent).GetTypeInfo().Assembly);

@johnnyoshika
Copy link
Author

Thank you @pranavkm. Everything compiles now, but unfortunately it still doesn't work. Here's the response I get on a page that contains a view component from another assembly:

HTTP/1.1 500 Internal Server Error
Content-Length: 0
Server: Kestrel
X-SourceFiles: =?UTF-8?B?QzpcVXNlcnNcSm9obm55XERvY3VtZW50c1xHaXRIdWJcZGVtb1xEZW1vLk1WQzZcd3d3cm9vdFxob21lXGxpYnJhcnk=?=
X-Powered-By: ASP.NET
Date: Thu, 17 Dec 2015 23:11:55 GMT

When I copy the cshtml into the proper directory in my startup web project, then it works, so the view component .cs file is registered properly, but not the cshtml file.

@pranavkm
Copy link
Contributor

Can you turn on the developer exception page and include the exception stack trace?

@johnnyoshika
Copy link
Author

@pranavkm: I think this is what you're looking for?

System.InvalidOperationException occurred
  HResult=-2146233079
  Message=The view 'Components/BookOfTheMonth/Default' was not found. The following locations were searched:
/Views/Home/Components/BookOfTheMonth/Default.cshtml
/Views/Shared/Components/BookOfTheMonth/Default.cshtml.
  Source=Microsoft.AspNet.Mvc.ViewFeatures
  StackTrace:
       at Microsoft.AspNet.Mvc.ViewEngines.ViewEngineResult.EnsureSuccessful()
       at Microsoft.AspNet.Mvc.ViewComponents.ViewViewComponentResult.FindView(ActionContext context, IViewEngine viewEngine, String viewName)
       at Microsoft.AspNet.Mvc.ViewComponents.ViewViewComponentResult.<ExecuteAsync>d__20.MoveNext()
    --- End of stack trace from previous location where exception was thrown ---
       at System.Runtime.CompilerServices.TaskAwaiter.ThrowForNonSuccess(Task task)
       at System.Runtime.CompilerServices.TaskAwaiter.HandleNonSuccessAndDebuggerNotification(Task task)
       at System.Runtime.CompilerServices.TaskAwaiter.GetResult()
       at Microsoft.AspNet.Mvc.ViewComponents.ViewViewComponentResult.Execute(ViewComponentContext context)
       at Microsoft.AspNet.Mvc.ViewComponents.DefaultViewComponentInvoker.Invoke(ViewComponentContext context)
       at Microsoft.AspNet.Mvc.ViewComponents.DefaultViewComponentHelper.InvokeCore(TextWriter writer, ViewComponentDescriptor descriptor, Object[] arguments)
       at Microsoft.AspNet.Mvc.ViewComponents.DefaultViewComponentHelper.Invoke(String name, Object[] arguments)
       at Asp.ASPV__Views_Home_Library_cshtml.<ExecuteAsync>d__17.MoveNext() in /Views/Home/Library.cshtml:line 9
  InnerException: 

Here's a screenshot as well in case it's useful:

image

Here's my code in Startup.cs

            services.Configure<RazorViewEngineOptions>(options =>
            {
                options.FileProvider = new CompositeFileProvider(
                    new EmbeddedFileProvider(
                        typeof(BookOfTheMonthViewComponent).GetTypeInfo().Assembly
                    ),
                    options.FileProvider
                );
            });

@pranavkm
Copy link
Contributor

Going by your image in the StackOverflow post, you would need to move the Shared directory to a directory named Views. (look at the paths it's looking for). In addition, you would need to specify a base namespace for the EmbeddedFileSystem. See https://github.com/aspnet/Mvc/blob/dev/samples/EmbeddedViewSample.Web/Startup.cs#L25. For instance in your case, the namespace would be BookStore.Components

@johnnyoshika
Copy link
Author

@pranavkm: thanks for noticing the error in my folder structure. I made the change and I added baseNamespace but I still get the same error.

System.InvalidOperationException occurred
  HResult=-2146233079
  Message=The view 'Components/BookOfTheMonth/Default' was not found. The following locations were searched:
/Views/Home/Components/BookOfTheMonth/Default.cshtml
/Views/Shared/Components/BookOfTheMonth/Default.cshtml.
  Source=Microsoft.AspNet.Mvc.ViewFeatures
  StackTrace:
       at Microsoft.AspNet.Mvc.ViewEngines.ViewEngineResult.EnsureSuccessful()
       at Microsoft.AspNet.Mvc.ViewComponents.ViewViewComponentResult.FindView(ActionContext context, IViewEngine viewEngine, String viewName)
       at Microsoft.AspNet.Mvc.ViewComponents.ViewViewComponentResult.<ExecuteAsync>d__20.MoveNext()
    --- End of stack trace from previous location where exception was thrown ---
       at System.Runtime.CompilerServices.TaskAwaiter.ThrowForNonSuccess(Task task)
       at System.Runtime.CompilerServices.TaskAwaiter.HandleNonSuccessAndDebuggerNotification(Task task)
       at System.Runtime.CompilerServices.TaskAwaiter.GetResult()
       at Microsoft.AspNet.Mvc.ViewComponents.ViewViewComponentResult.Execute(ViewComponentContext context)
       at Microsoft.AspNet.Mvc.ViewComponents.DefaultViewComponentInvoker.Invoke(ViewComponentContext context)
       at Microsoft.AspNet.Mvc.ViewComponents.DefaultViewComponentHelper.InvokeCore(TextWriter writer, ViewComponentDescriptor descriptor, Object[] arguments)
       at Microsoft.AspNet.Mvc.ViewComponents.DefaultViewComponentHelper.Invoke(String name, Object[] arguments)
       at Asp.ASPV__Views_Home_Library_cshtml.<ExecuteAsync>d__17.MoveNext() in /Views/Home/Library.cshtml:line 9
  InnerException: 

Here's my updated fileprovider registration:

            services.Configure<RazorViewEngineOptions>(options =>
            {
                options.FileProvider = new CompositeFileProvider(
                    new EmbeddedFileProvider(
                        typeof(BookOfTheMonthViewComponent).GetTypeInfo().Assembly,
                        "BookStore.Components"
                    ),
                    options.FileProvider
                );
            });

Updated folder structure:

image

@johnnyoshika
Copy link
Author

I also tried renaming the Shared folder to Home and it still didn't work.

@pranavkm
Copy link
Contributor

@johnnyoshika
Copy link
Author

Wow, this must be my missing piece. Would I add this to my startup web project's project.json or the project where the component is defined? I've tried a few combinations but they haven't worked yet.

@d0pare
Copy link

d0pare commented Dec 18, 2015

I guess you should add in the class library's project.json

@johnnyoshika
Copy link
Author

Added this to class library's project.json but no luck. :-(

  "resource": "Views/**"

@johnnyoshika
Copy link
Author

I take back what I said. I created a new solution with only the absolute minimum parts, and it works. I shared this solution on GitHub:

https://github.com/johnnyoshika/mvc6-view-components

Now I have to figure out why this technique doesn't work in my other project. :-(

@pranavkm
Copy link
Contributor

Very cool!

@Morgma
Copy link

Morgma commented Jan 6, 2016

I assume this should work just fine for page views as well as components right? I'm trying to automatically add file providers from referenced assemblies that contain views.

In my Startup.cs -

public void ConfigureServices(IServiceCollection services) {
        var libNames = _libraryManager.GetLibraries().Select(lib => lib.Name).Distinct();
        var assemblies = new List<Assembly>();
        var fileProviders = new List<IFileProvider>();

        foreach (var libName in libNames.Where(libName => libName.StartsWith("Module")))
        {
            Assembly assm;
            try
            {
                assm = Assembly.Load(new AssemblyName(libName));
            }
            catch
            {
                continue;
            }

            assemblies.Add(assm);
            fileProviders.Add(new EmbeddedFileProvider(assm));
        }

        services.AddMvc()
                .AddPrecompiledRazorViews(assemblies.ToArray());

        services.Configure<RazorViewEngineOptions>(options =>
        {
            fileProviders.Add(options.FileProvider);
            options.FileProvider = new CompositeFileProvider(fileProviders.ToArray());
        });

    }

I realize I'm not passing in the baseNamespace parameter to EmbeddedFileProvider(). I've tried it both ways, but it hasn't changed the behavior.

Here is my class library package containing the view and controller I want to reference.

image

This is what the package .dll contains for my embedded view resource:
image

So my controller is attempting to explicitly call the view by this resource name:
image

However, still no luck in resolving the view:
image

My wwwroot project dependencies:

    "dependencies": {
        "Microsoft.AspNet.Diagnostics": "1.0.0-rc1-final",
        "Microsoft.AspNet.FileProviders.Composite": "1.0.0-rc2-*",
        "Microsoft.AspNet.FileProviders.Embedded": "1.0.0-rc2-*",
        "Microsoft.AspNet.IISPlatformHandler": "1.0.0-rc1-final",
        "Microsoft.AspNet.Mvc": "6.0.0-rc1-final",
        "Microsoft.AspNet.Server.Kestrel": "1.0.0-rc1-final",
        "Microsoft.AspNet.StaticFiles": "1.0.0-rc1-final",
        "Microsoft.Extensions.PlatformAbstractions": "1.0.0-rc1-final",
        "Module.StatusMonitor": "1.0.0-*",
        "System.Reflection": "4.1.0-beta-23516"
    }

... and my module package library dependencies:

    "dependencies": {
        "Microsoft.AspNet.Mvc": "6.0.0-rc1-final",
        "Microsoft.CSharp": "4.0.1-rc2-*",
        "System.Collections":  "4.0.11-rc2-*",
        "System.Linq":  "4.0.1-rc2-*",
        "System.Runtime": "4.0.21-rc2-*",
        "System.Threading": "4.0.11-rc2-*"
    }

Any thoughts or suggestions are much appreciated!

@johnnyoshika
Copy link
Author

@Morgma: I got page views from separate assemblies to load without any problem using the same technique as view components. I describe it here:

http://stackoverflow.com/a/34366119/188740

And here's a working example:

https://github.com/johnnyoshika/mvc6-view-components

@Morgma
Copy link

Morgma commented Jan 6, 2016

@johnnyoshika I had been working off your solution previously, but now revisiting it, I have found my issue. The baseNamespace is definitely required to make this work. Additionally, my approach to using ILibraryManager to find the assemblies doesn't work. My guess is that even though I reference the module in project.json and the assembly is found in library manager, the compiler never loads the assembly unless there is a reference to the namespace in a "using". I was hoping to get to a point where I could install the module NuGet and automatically handle everything I needed to essentially load a new "Area" within the main web project.

Finally, for some reason, Visual Studio is working against me by intellisense declaring the usings unnecessary, though the solution builds fine despite the red and ultimately does work. This is even in the case of a fresh clone of your solution:

image

Thanks for your code; helped me through these gotchas.

@Morgma
Copy link

Morgma commented Jan 6, 2016

Now that I've determined my root issue, though it was the first thing I discounted in my initial post, is that the defaultNamespace parameter was not set in my EmbeddedFileProvider. I was able to return to dynamically loading the assemblies based on the convention of starting with "Module" and all is working well. Thanks again for your help @johnnyoshika !

Here's an example of how I'm loading in all of my View providers:

        public void ConfigureServices(IServiceCollection services)
        {

            services.AddMvc();

            var libNames = _libraryManager.GetLibraries().Select(lib => lib.Name).Distinct();
            var assemblies = new List<Assembly>();
            var fileProviders = new List<IFileProvider>();

            foreach (var libName in libNames.Where(libName => libName.StartsWith("Module")))
            {
                Assembly assm;
                try
                {
                    assm = Assembly.Load(new AssemblyName(libName));
                }
                catch
                {
                    continue;
                }

                assemblies.Add(assm);
                var defaultNamespace = assm.FullName.Split(',')[0];

                fileProviders.Add(new EmbeddedFileProvider(assm, defaultNamespace));
            }

            services.Configure<RazorViewEngineOptions>(options =>
            {
                fileProviders.Add(options.FileProvider);
                options.FileProvider = new CompositeFileProvider(fileProviders.ToArray());
            });

        }

@johnnyoshika
Copy link
Author

@Morgma: fantastic!

@apoutney
Copy link

Great solution, exactly what I was looking for.

What about things other than views though. How would you access things such as js, css or image files in the wwwroot of the external assembly?

@pranavkm
Copy link
Contributor

The StaticFileMiddleware allows specifying a file provider. Perhaps specifying the CompositeFileProvider as it's file system view would suffice?

@Morgma
Copy link

Morgma commented Jan 11, 2016

@pranavkm In my case, the external assemblies are not standalone web projects with a wwwroot; just NuGet package class libraries. Within the same project source, I'm exposing a Bower package that will supply the static files. My core web app will use Gulp to provision these dependencies to the right locations and do any transformations (script ordering, namespacing, etc...)

I'm still working through some implementation details, but seems to be a viable approach to getting to the level of isolation I need between modules.

@pranavkm
Copy link
Contributor

The idea would be to embed resources in the class library and register a static file middleware with a composite file system - similar to the way the composite file system for views is created.,

@apoutney
Copy link

Thanks for the advice. I had tried the following in the Configure method to use a composite file provider for static files in

app.UseStaticFiles(new StaticFileOptions()
{
      FileProvider = new CompositeFileProvider(fileProviders.ToArray()),
});

But Im not sure how to then access the static files from client side code, whatever I try just results in a 404 error. Im not sure if im embeddingthe files as resources properly in my project.json or if im adding the file properly in my html.

I will give @Morgma approach a try but I havent used Gulp before and was trying to get around having to write a load of scripts to pull the files in as this would mean possibly having to update those scripts when changes are made to the external assembly.

I was hoping there would be a way to do this without using scripts to bring the files in

@dazinator
Copy link

I am hitting an issue where external View components only appear to work when there is an assembly / project reference to the VC from the website project.

If you are just dropping an assembly containing the VC (And it's embedded razor view) into some folder, and the website is dynamically discovering the assembly when starting up, then it stops working. I'll put together a repro on github to demonstrate.

@dazinator
Copy link

Repro here: https://github.com/dazinator/mvc6-view-components/tree/issue-noprojectreferences

I simply forked @johnnyoshika working example, removed the project references and loaded the assemblies (for the external VC, and for the external Controller) dynamically instead.

The external controller still works, the VC doesn't..

@johnnyoshika
Copy link
Author

@dazinator: That's interesting. I wonder why your technique doesn't work.

@vikekh
Copy link

vikekh commented Feb 8, 2016

I've been pulling my hair over this for over a week now and I cannot get it to work. I have a web project Vikekh.Web in which I am adding one EmbeddedFileProvider, supplying the constructor with the assembly derived from my module's controller and the default namespace.

var fileProviders = new List<IFileProvider>();
var moduleAssembly = typeof(Vikekh.Modules.ModuleA.Controllers.ModuleAController).GetTypeInfo().Assembly;

fileProviders.Add(new EmbeddedFileProvider(
    moduleAssembly,
    moduleAssembly.FullName.Split(',')[0]
));

services.Configure<RazorViewEngineOptions>(o =>
{
    o.FileProvider = new CompositeFileProvider(fileProviders);
});

The module project has its views in Vikekh.Modules.ModuleA\Areas\ModuleA\Views\ModuleA and I have specified the following in the project.config:

"resource": [ "Areas/ModuleA/Views/**", "Areas/ModuleA/Content/**" ]

But when I try to access I get a 500 Internal Server Error and:

InvalidOperationException: The view 'Foo' was not found. The following locations were searched:
/Views/ModuleA/Foo.cshtml
/Views/Shared/Foo.cshtml.

The controller action is hit. Any ideas? I am on *-rc1-final, but had to pull in Microsoft.AspNet.FileProviders.Composite at rc2 from the aspnetvnext feed.

@dazinator
Copy link

@vikekh

You might want to use the default file provider, in addition to your own. At the moment I think you are overwriting the default file provider.

            var embeddedFileProviders = componentAssemblies.Select(a => (IFileProvider)new EmbeddedFileProvider(a, a.GetName().Name)).ToList();
            services.Configure<RazorViewEngineOptions>(options =>
            {
                embeddedFileProviders.Add(options.FileProvider);
                options.FileProvider = new CompositeFileProvider(embeddedFileProviders);
            });

Also you are using areas, did you set up a route?

@pranavkm
Copy link
Contributor

pranavkm commented Feb 8, 2016

@vikekh does your controller need to be marked with an AreaAttribute? Your project.json includes views from the Areas/ModuleA/Views, but the view engine is looking for non-area views.

@dazinator
Copy link

@johnnyoshika

That's interesting. I wonder why your technique doesn't work.

I have no idea why it doesn't work haha. I was hoping someone can shed some light on it or perhaps there is something I was missing. I have a feeling its a genuine MVC 6 issue though.

The only thing I can think of is that it't an oversight, in that there is a method for registering controllers in stand alone assemblies, but nothing for doing the same for VC's.

I.e there is no equivalent to the following, for VC's

services.AddMvc().AddControllersAsServices(new[] { assembly });

If that's correct, I'm assuming there must be some other class you need to implement to provide the candidate assemblies for VC discovery, but I am not sure what that is!

@vikekh
Copy link

vikekh commented Feb 9, 2016

@dazinator I added the default file provider, however it always seems to be null.

@pranavkm Having an area attribute makes the controller method unreachable.

There may be other custom implementations in the web project since I'm not the one who set it up. However, I am responsible for upgrading from Beta 7 to RC1. Previously the path Vikekh.Modules.ModuleA\Areas\ModuleA was added as a PhysicalFileProvider but this doesn't seem to work anymore--therefor I am attempting this solution.

@dazinator
Copy link

@vikekh about the null file provider.. that definately shouldn't be the case and i also hit that samr problem when I was using autofac. The solution for me was:

Something you can try is to move your services.ConfigureOptions call above services.AddMvc() to see if it makes a difference.

Full discussion here

@vikekh
Copy link

vikekh commented Feb 9, 2016

@dazinator Already is above!

@dazinator
Copy link

@vikekh
:-(

@pranavkm
Copy link
Contributor

pranavkm commented Feb 9, 2016

I.e there is no equivalent to the following, for VC's services.AddMvc().AddControllersAsServices(new[] { assembly });

@vikekh You would have to register a StaticAssemblyProvider to do this. It's a bit tangential to this issue, could you start a new issue for it?

@dazinator
Copy link

@pranavkm - it was actually me who mentioned that! I have just raised it as a seperate issue as requested #4087

@Genbox
Copy link

Genbox commented Jul 31, 2016

Any updates on this? There have been some changes to the framework and the solution presented does no longer work. Please provide guidance on adding external views/controllers and view components.

@dazinator
Copy link

@Genbox MVC 6 rtm has a new "application parts" API. All you need to do is register your assembly containg your viewcomponent with that Api. If you assembly is allready referenced then you shouldnt need to bother, this is just if you load assemblies at runtime.

@Genbox
Copy link

Genbox commented Jul 31, 2016

@dazinator I'm loading assemblies at runtime. Could you point me in the direction of this API? A Google search for application parts was unfruitful.

@dazinator
Copy link

@Genbox from memory you can do something like:

Assembly someassembly = GetAssembly();
services.AddMvc().AddApplicationPart(assembly);

So in other words the Api is on the builder returned via AddMvc();

@Genbox
Copy link

Genbox commented Aug 3, 2016

This seem to work fine with controllers, but what about views?

Edit: Searched a bit for AddApplicationPart() and this article came up: http://www.codeproject.com/Articles/1109475/WebControls/ it also addresses how the view discovery works.

@dazinator
Copy link

I think this can be closed now, as it has been established that VC's do work in seperate assemblies..

@johnnyoshika
Copy link
Author

@dazinator I just tried loading view components from another assembly with the newly released VS 2017, and it looks like the views aren't being discovered. I get the standard old error message:

InvalidOperationException: The view 'Components/CategoryDetails/Default' was not found. The following locations were searched:
/Views/Home/Components/CategoryDetails/Default.cshtml
/Views/Shared/Components/CategoryDetails/Default.cshtml

The separate assembly is being referenced. I also tried services.AddMvc().AddApplicationPart(assembly) but it still doesn't work. Without the view, the View Component loads just fine (e.g. if I return ContentViewComponentResult instead of ViewViewComponentResult). Thoughts?

@dazinator
Copy link

dazinator commented Mar 9, 2017

Not taken the plunge and upgraded to vs2017 yet. I'm still in project.json land, and targeting asp.net core 1.0.

Some things you might want to check though:

  1. Are your views definitely embedded? Open your module assembly with justdecompile (or something similar) and verify that they are there.
  2. Are you setting up.your embedded file provider with a namespace? Compare that with the embedded resource path of your view in your module assembly - the remaining bit is the "subpath" of your view.
  3. I actually set IHostingEnvironment.ContentRoot (name might not be exact as from memory) to a composite file provider - not razor view options.
  4. Given all of the above, on startup, you should be able to test that your Cshtml is resolvable by accessing your composite file provider (in my case this is the IHostingEnvironments
    ContebntRoot FileProvider) and test getting the file by its subpath. I.e fileProvider.GetFileInfo("/some/module/foo.cshtml"). The file info returned should have an Exists property and the ability to create a read stream so you can test reading the file.

If you can resolve the view using your FileProvider then I'm thinking your issue is probably about getting MVC to use your file provider for view resolution. If you can't then I'm guessing you have an issue with your file provider setup.

@johnnyoshika
Copy link
Author

@dazinator I got it to work. Thanks for all of the tips. Things have changed a bit with MS Build.

In the external assembly, we need to include this in csproj:

<ItemGroup>
   <EmbeddedResource Include="Views/**/*.cshtml" />
</ItemGroup>

Then in the executing project, we need to reference this NuGet package: Microsoft.Extensions.FileProviders.Embedded

Then in Startup, we can do this:

            services.Configure<RazorViewEngineOptions>(options =>
            {
                options.FileProviders.Add(new EmbeddedFileProvider(
                    assembly
                ));
            });

@johnnyoshika
Copy link
Author

johnnyoshika commented Mar 9, 2017

Interestingly, changing views in external assemblies doesn't get reflected right away. i.e. reloading the browser doesn't reflect any changes. It requires a full compile for the changes to show up. So it looks like this problem hasn't been fully addressed yet, although it's better than it was before. Before even a compile wasn't enough...you actually had to change and save a C# file.

@dazinator
Copy link

dazinator commented Mar 9, 2017

Are you using embedded views? I solved this problem by only using Embedded views for production builds. When running under development mode, rather than creating EmbeddedFileProvider's that wrap the assembly , I create PhsyicalFileProviders that point to the actual project source code location as the content root folder. This has the benefit that you can just edit the cshtml view that's in the separate project, and it will update in real time without needing to build. Ofcourse when you deploy your application, you won't be deploying all of the source code for these projects, and that's when you'd fall back to using embedded file providers instead.

@rynowak
Copy link
Member

rynowak commented Mar 9, 2017

Are you using embedded views? I solved this problem by only using Embedded views for production builds.

Sadly this is the reality for embedded views. They are baked into the assembly at compile-time, and we won't ever look on disk because we don't know where to look.

@johnnyoshika
Copy link
Author

Are you using embedded views? I solved this problem by only using Embedded views for production builds. When running under development mode, rather than creating EmbeddedFileProvider's that wrap the assembly , I create PhsyicalFileProviders that point to the actual project source code location as the content root folder. This has the benefit that you can just edit the cshtml view that's in the separate project, and it will update in real time without needing to build. Ofcourse when you deploy your application, you won't be deploying all of the source code for these projects, and that's when you'd fall back to using embedded file providers instead.

Here's a sample code of @dazinator's technique:

if (Env.IsDevelopment())
    services.Configure<RazorViewEngineOptions>(options =>
    {
        options.FileProviders.Add(new PhysicalFileProvider(Regex.Replace(Env.ContentRootPath, $"{Env.ApplicationName}$", "PortalComponents")));
    });
else
    services.Configure<RazorViewEngineOptions>(options =>
    {
        options.FileProviders.Add(new EmbeddedFileProvider(
            typeof(CategoryDetails).GetTypeInfo().Assembly,
            "PortalComponents"
        ));
    });

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

No branches or pull requests

10 participants