natemcmaster / DotNetCorePlugins

.NET Core library for dynamically loading code
Apache License 2.0
1.59k stars 227 forks source link

View not found in a .net 5 web mvc application using the mvc sample plugin #170

Closed davidbuckleyni closed 2 years ago

davidbuckleyni commented 3 years ago

I am using .net 5 web site mvc to build a wms system with plugins and I am using your plugin for that abilties to make it easier to consume the plugins however the view is just not being hit.

This is my full startup.css I have also created a directory called plugins and made sure it copied to the bin directory. One thing I did was test with the sample you had so I copied MvcAppPlugin1 and copied it to my bin \ plugins directory, However when I look at the goto the MyPlugin action ie localhost/MyPlugin nothing is display i logged into my system as it uses authorisation so i no nothing is going wrong that way.

I noticed when I compiled the app it created to binaries should the view be embbed so it doesnt produce the second dll.

image

public class Startup {
        private string _crmAPiKey = null;
        private string extensionsPath;

        public Startup(IWebHostEnvironment webHostEnvironment, IConfiguration configuration) {
            Configuration = configuration;
            this.extensionsPath = webHostEnvironment.ContentRootPath + configuration["Extensions:Path"];

        }

        public IConfiguration Configuration { get; }

        // This method gets called by the runtime. Use this method to add services to the container.
        public void ConfigureServices(IServiceCollection services) {
            services.AddLocalization(options => options.ResourcesPath = "Resources");
            _crmAPiKey = Configuration["CrmApiKey:ServiceApiKey"];

            services.AddSingleton<LocalizationService>();

            services.AddDbContext<WarehouseDBContext>
        (options => options.UseSqlServer(Configuration.GetConnectionString("DefaultConnection")), ServiceLifetime.Transient);
            services.AddRepository();

            services.AddIdentity<ApplicationUser, IdentityRole>(config => {
                config.SignIn.RequireConfirmedEmail = true;
                config.Tokens.AuthenticatorTokenProvider = TokenOptions.DefaultAuthenticatorProvider;
                config.User.RequireUniqueEmail = true;

            })
            .AddDefaultUI()

           .AddRoles<IdentityRole>()
           .AddDefaultTokenProviders()
           .AddEntityFrameworkStores<WarehouseDBContext>();
                services.AddControllersWithViews()
            .AddDataAnnotationsLocalization();
            services.Configure<CookiePolicyOptions>(options => {
                options.CheckConsentNeeded = context => true; // consent required
                options.MinimumSameSitePolicy = SameSiteMode.None;
            });
            services.AddSession(opts => {
                opts.Cookie.IsEssential = true; // make the session cookie Essential
            });

            services.AddRazorPages()
            .AddSessionStateTempDataProvider();

            services.AddMvc().AddNToastNotifyToastr(new ToastrOptions() {
                ProgressBar = false,
                PositionClass = ToastPositions.TopRight,
                ToastClass = "alert",

            });

            services.Configure<RequestLocalizationOptions>(options => {
                var supportedCultures = new[]
                {
                    new CultureInfo("en-US"),
                    new CultureInfo("en-GB"),
                    new CultureInfo("es"),
                    new CultureInfo("fr")
                    };

                options.DefaultRequestCulture = new RequestCulture(culture: "en-GB", uiCulture: "en-GB");
                options.SupportedCultures = supportedCultures;
                options.SupportedUICultures = supportedCultures;
                options.RequestCultureProviders = new List<IRequestCultureProvider>
        {
              new QueryStringRequestCultureProvider(),
              new CookieRequestCultureProvider()
              };
            }).AddControllersWithViews()

                .AddViewLocalization(LanguageViewLocationExpanderFormat.SubFolder)
    .AddViewLocalization(options => options.ResourcesPath = "Resources")
                .AddDataAnnotationsLocalization(options => {
                    options.DataAnnotationLocalizerProvider = (type, factory) => {
                        var assemblyName = new AssemblyName(typeof(SharedResource).GetTypeInfo().Assembly.FullName);
                        return factory.Create("SharedResource", assemblyName.Name);
                    };
                });

            services.AddControllers(config => {
                // using Microsoft.AspNetCore.Mvc.Authorization;
                // using Microsoft.AspNetCore.Authorization;
                var policy = new AuthorizationPolicyBuilder()
                                 .RequireAuthenticatedUser()
                                 .Build();
                config.Filters.Add(new AuthorizeFilter(policy));
            });

            services.Configure<IdentityOptions>(options => {

                // Default Lockout settings.
                options.Lockout.DefaultLockoutTimeSpan = TimeSpan.FromMinutes(5);
                options.Lockout.MaxFailedAccessAttempts = 5;
                options.Lockout.AllowedForNewUsers = true;

            });
            services.ConfigureApplicationCookie(config => {
                config.Cookie.Name = "Identity.Cookie";
                config.LoginPath = "/Identity/Account/Login/";
            });
            //services.AddTransient<MISDBContextSeedData>();
            services.AddSingleton<ISharedResource, SharedResource>();
            // using WebPWrecover.Services;
            services.AddTransient<IEmailSender, EmailSender>();

            services.Configure<AuthMessageSenderOptions>(Configuration);

            services.AddAutoMapper
(typeof(AutoMapperProfile).Assembly);

            services.AddAuthorization(options =>
      options.AddPolicy("TwoFactorEnabled",
          x => x.RequireClaim("amr", "mfa")));
            services.AddAntiforgery(o => o.HeaderName = "XSRF-TOKEN");
            var mvcBuilder = services
         .AddMvc()
         .SetCompatibilityVersion(CompatibilityVersion.Version_3_0);

            foreach (var dir in Directory.GetDirectories(Path.Combine(AppContext.BaseDirectory, "plugins")))
            {
                var pluginFile = Path.Combine(dir, Path.GetFileName(dir) + ".dll");
                // The AddPluginFromAssemblyFile method comes from McMaster.NETCore.Plugins.Mvc
                mvcBuilder.AddPluginFromAssemblyFile(pluginFile);
            }
            services.AddAuthentication(options => {
                options.DefaultAuthenticateScheme = IdentityConstants.ApplicationScheme;
                options.DefaultChallengeScheme = IdentityConstants.ApplicationScheme;
                options.DefaultSignInScheme = IdentityConstants.ExternalScheme;
            });
        }

        // This method gets called by the runtime. Use this method to configure the HTTP request pipeline.
        public void Configure(IApplicationBuilder app, IWebHostEnvironment env) {
            var locOptions = app.ApplicationServices.GetService<IOptions<RequestLocalizationOptions>>();
            app.UseRequestLocalization(locOptions.Value);

   //         seeder.SeedAdminUser();//this is my super user account do not over write
     //       seeder.SeedUser1();
       //     seeder.SeedUser2();
         //   seeder.SeedUserManager();

            var supportedCultures = new[]
            {
        new CultureInfo("en-US"),
        new CultureInfo("en-GB"),
        new CultureInfo("es"),
        new CultureInfo("fr")
        };
            app.UseRequestLocalization(new RequestLocalizationOptions() {
                DefaultRequestCulture = new RequestCulture("en-GB"),
                SupportedCultures = supportedCultures,
                SupportedUICultures = supportedCultures
            });
            if (env.IsDevelopment()) {
                app.UseDeveloperExceptionPage(new DeveloperExceptionPageOptions() { SourceCodeLineCount = 30 });
                app.UseDatabaseErrorPage();
            } else {
                //    app.UseExceptionHandler("/Home/Error");
                // The default HSTS value is 30 days. You may want to change this for production scenarios, see https://aka.ms/aspnetcore-hsts.
                app.UseHsts();
            }
             app.UseCookiePolicy();

            app.UseNToastNotify();

            app.UseSession();
            app.UseHttpsRedirection();
            app.UseStaticFiles();
            app.UseRouting();
            app.UseAuthentication();
            app.UseAuthorization();

            app.UseEndpoints(endpoints => {
                endpoints.MapControllerRoute(
                    name: "default",
                    pattern: "{controller=Home}/{action=Index}/{id?}");
                endpoints.MapRazorPages();
            });

        }
    }
natemcmaster commented 3 years ago

I'm not sure what's wrong. Perhaps something changed in .NET 5. Marking as help-wanted. If someone can identify the root cause and propose a fix, I'd appreciate the help.

GiampaoloGabba commented 3 years ago

The only way i found to use views for a MVC plugin in .NET5 is:

  1. Reference Microsoft.AspNetCore.Mvc.Razor.RuntimeCompilation in the main project: <PackageReference Include="Microsoft.AspNetCore.Mvc.Razor.RuntimeCompilation" Version="5.0.1" />

  2. Use .AddRazorRuntimeCompilation() in the mvcBuilder:

    var mvcBuilder = services.AddControllersWithViews()
                           .AddRazorRuntimeCompilation();
  3. Use .AddRazorOptions to add my custom path for resolving views:

    foreach (var dir in Directory.GetDirectories(_extensionsPath))
    {
    var pluginFile = Path.Combine(dir, Path.GetFileName(dir) + ".dll");
    mvcBuilder.AddPluginFromAssemblyFile(pluginFile);
    
    mvcBuilder.AddRazorOptions(o =>
    {
        o.ViewLocationFormats.Add("/Plugins/" + new DirectoryInfo(dir).Name + "/Views/{1}/{0}.cshtml");
        o.ViewLocationFormats.Add("/Plugins/" + new DirectoryInfo(dir).Name + "/Views/Shared/{0}.cshtml");
    });
    }
  4. Configure the plugin project to copy your views in the outputpath:

    <ItemGroup>
    <Content Include="Views\**\*.cshtml">
        <CopyToOutputDirectory>Always</CopyToOutputDirectory>
    </Content>
    </ItemGroup>

With these steps, you also have full access to views, components, PartialViews, ecc... from the plugin in your main project, for example:

@await Html.PartialAsync($"~/Plugins/PluginName/Views/Shared/view.cshtml"

aloksharma1 commented 3 years ago

@GiampaoloGabba can you make a sample repo, i tried this but its not working for me... weirdly i dont need it for a single plugin host application but all other plugins view are called then i get this error InvalidOperationException: Cannot find compilation library location for package 'System.LoginModule' which is the first module, it does hits the desired view on debug but afterwards breaking like this... any suggestion?

davidbuckleyni commented 3 years ago

I already closed this

aloksharma1 commented 3 years ago

@davidbuckleyni were you able to solve it?

davidbuckleyni commented 3 years ago

@davidbuckleyni were you able to solve it?

I actually ended up downloaded the code for SimplComerce it highlights allot of code that is missing from this example.

github-actions[bot] commented 2 years ago

This issue has been automatically marked as stale because it has no recent activity. It will be closed if no further activity occurs. Please comment if you believe this should remain open, otherwise it will be closed in 14 days. Thank you for your contributions to this project.

github-actions[bot] commented 2 years ago

Closing due to inactivity. If you are looking at this issue in the future and think it should be reopened, please make a commented here and mention natemcmaster so he sees the notification.