Search code examples
c#asp.net-core-mvcasp.net-identityasp.net-core-2.1

MVC Core, changed root view folder to UI, erroring with "The default Identity UI layout requires a partial view '_LoginPartial'"


I've changed my directory structure for my MVC Core 2.2 site. I have place the View information in a UI folder off of the root folder. So my folder structure is as follows:

UI +- Areas
   +- Controllers
   +- ViewHelpers
   +- ViewModels
   +- View
      +- Account
       ...
      +- Shared

Now, I am getting the following error when I navigate to a view whose controller has an [Authorize]. If I remove the authorize attribute, the error disappears. The error is as follows:

InvalidOperationException: The default Identity UI layout requires a partial view '_LoginPartial' usually located at '/Pages/_LoginPartial' or at '/Views/Shared/_LoginPartial' to work. Based on your configuration we have looked at it in the following locations:
/Areas/Identity/Pages/Account/_LoginPartial.cshtml
/Areas/Identity/Pages/_LoginPartial.cshtml
/Areas/Identity/Pages/Shared/_LoginPartial.cshtml
/Areas/Identity/Views/Shared/_LoginPartial.cshtml
/Pages/Shared/_LoginPartial.cshtml
/Views/Shared/_LoginPartial.cshtml.
Microsoft.AspNetCore.Identity.UI.V4.Pages.Internal.Areas_Identity_Pages__Layout.<ExecuteAsync>b__42_1()

In my ConfigureServices method I have the following,

public void ConfigureServices(IServiceCollection services)
{
    ...
    services.Configure<RazorViewEngineOptions>(o =>
    {
        o.ViewLocationFormats.Clear();
        o.ViewLocationFormats.Add("/UI/Views/{1}/{0}" + RazorViewEngine.ViewExtension);
        o.ViewLocationFormats.Add("/UI/Views/Shared/{0}" + RazorViewEngine.ViewExtension);
        o.PageViewLocationFormats.Add("/UI/Areas/Identity/Pages/{1}/{0}" + RazorViewEngine.ViewExtension);
    });
    services.AddSession();
}

It would be great if I could also understand what is happening.


Solution

  • This is my solution to moving the Identity Scaffolding code.

    I am trying to use Clear Architecture in the conversion of a .Net Framework MVC/API/Identity site to a MVC Core 2.2 site. My inspiration come from the following video: Clean Architecture with ASP.NET Core 2.2 - Jason Taylor

    My version of the directory structure is as follow:

    \+- Application 
     |  +- Commands
     +- Domain
     |  +- Entities
     +- Infrastructure
     +- UI
        +- Identity
        |  +- Pages
        |     +- Account
        |      ...
        +- Controllers
        +- ViewHelpers
        +- ViewModels
        +- View
           +- Company
               ...
              +- Shared
    

    In my app, I changed the IdentityUser and IdentityRole classed and the Scaffolding Identity directory did not fit the above. Unfortunately I could not find an example on how to handle the default Identity directory.

    My solution was to create the Scaffolding Identity and convert from an Area to a Page format. This is a process with a lot of search and replace, and fraught with problems.

    The first step is to create the directory structure. The next step is to create the Scaffolding Identity. Many how-tos exist, but the Microsoft docs works for me. You must get this working first, before moving the folder! To getting Identity to work, you will need to search and replace UserManager<IdentityUser> and SignInManager<IdentityUser>. Do a final search (only) for IdentityUser and manually fix up the DownloadPersonalDataModel page.

    Add using statement to the razor pages that reference ApplicationUser:

    using .Domain.Entities;

    and to _LoginPartial

    @using .Domain.Entities

    Test it.

    Drag the Controllers/Views/Models folder under the UI folder. I renamed Models to ViewModels (optional). Search and replace the namespaces to the new namespaces.

    • .Controllers -> .UI.Controllers
    • .Models -> .UI.ViewModels (in _ViewImports.cshtml)
    • /Views/Shared/ -> /UI/Views/Shared/

    Add the Razor View engine options to search the new UI path for view/page/area as follows:

    services.Configure<RazorViewEngineOptions>(o =>
    {
        // {2} is area, {1} is controller,{0} is the action
        o.ViewLocationFormats.Clear();
        o.ViewLocationFormats.Add("/UI/Views/{1}/{0}" + RazorViewEngine.ViewExtension);
        o.ViewLocationFormats.Add("/UI/Views/Shared/{0}" + RazorViewEngine.ViewExtension);
        // now razor pages
        o.PageViewLocationFormats.Clear();
        o.PageViewLocationFormats.Add("/UI/Views/{1}/{0}" + RazorViewEngine.ViewExtension);
        o.PageViewLocationFormats.Add("/UI/Views/Shared/{0}" + RazorViewEngine.ViewExtension);
        o.PageViewLocationFormats.Add("/UI/Identity/Pages/Account/Manage/{0}" + RazorViewEngine.ViewExtension);
        //
        o.AreaViewLocationFormats.Clear();
        o.AreaViewLocationFormats.Add("/UI/Views/{1}/{0}" + RazorViewEngine.ViewExtension);
        o.AreaViewLocationFormats.Add("/UI/Views/Shared/{0}" + RazorViewEngine.ViewExtension);
        // now razor areas
        o.AreaPageViewLocationFormats.Add("/UI/Views/{1}/{0}" + RazorViewEngine.ViewExtension);
        o.AreaPageViewLocationFormats.Add("/UI/Views/Shared/{0}" + RazorViewEngine.ViewExtension);
        Console.WriteLine(o);
    });
    

    If you get a 'The following locations were searched:' error message, the Console.WriteLine in the above is a great place to place a break point to see what is available and what is set.

    Test the page access. When everything is clean, then you can drag the Identity folder into the **UI* folder. Search and replace the namespace to the new namespace. - .Areas.Identity -> .UI.Identity

    Change the _LoginPartial and search and replace Areas (to remove area): - asp-area="Identity" -> asp-area=""

    Change the MVC Razor options as follows:

    services.AddMvc()
        .SetCompatibilityVersion(CompatibilityVersion.Version_2_1)
            .AddRazorPagesOptions(options =>
            {
                options.AllowAreas = false;
                options.Conventions.Clear();
                options.RootDirectory = "/UI/Identity/Pages";
                Console.WriteLine(options);
            });
    

    Change the Razor View engine options as follows:

    services.Configure<RazorViewEngineOptions>(o =>
    {
        ...
        // now razor areas
        o.AreaPageViewLocationFormats.Clear();
        o.AreaPageViewLocationFormats.Add("/UI/Views/{1}/{0}" + RazorViewEngine.ViewExtension);
        o.AreaPageViewLocationFormats.Add("/UI/Views/Shared/{0}" + RazorViewEngine.ViewExtension);
        o.AreaPageViewLocationFormats.Add("/UI/Identity/Account/Manage/{0}" + RazorViewEngine.ViewExtension);
        Console.WriteLine(o);
    }
    

    Now try it... the path to the Identity pages is no longer /Identity/Account, but it is now /Account