Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

c# View calling Angular component breaks, but calling Angular directly works fine

How do I fix my routing? I have a C# project with an Angular front-end. If I go to a c# View which calls an Angular component everything breaks. If I call an Angular view (directly from the URL) everything works fine.

C# routing to a c# view

  • If I route properly in startup.cs I go to: xxx/Home/index which is simply a View that calls an Angular component (which throws a bunch of 500 errors)

Manually routing to Angular

  • If I manually add /anything to the url (xxx/Home/Index/anything) the Angular routing takes over and everything loads fine.

Index method call

public class HomeController : Controller
{
public IActionResult Index()
{
    return View("IndexAng");
}
}

IndexAng.cshtml

@{
    ViewData["Title"] = "Home Page";
}

@*<script src="https://npmcdn.com/[email protected]/dist/js/tether.min.js"></script>*@
@*<app asp-prerender-module="ClientApp/dist/main-server">Loading...</app>*@
<h3>Loading Ang App root:</h3>
<app-root></app-root>
<script src="~/dist/vendor.js" asp-append-version="true"></script>
@section scripts {
    <script src="~/dist/main-client.js" asp-append-version="true"></script>
}

errors when calling the c# routing: enter image description here

Configure method from Startup.cs

public void Configure(IApplicationBuilder app, IHostingEnvironment env, ApplicationDbContext identityContext,
                    UserManager<ApplicationUser> userManager, RoleManager<IdentityRole> roleManager)
        {

#if DEBUG
            if (env.IsDevelopment())
            {
                app.UseDeveloperExceptionPage();
                app.UseWebpackDevMiddleware(new WebpackDevMiddlewareOptions
                {
                    HotModuleReplacement = true
                });
            }
            else
            {
                app.UseExceptionHandler("/Home/Error");
            }
#else
            app.UseExceptionHandler("/Home/Error");
#endif
            app.UseStaticFiles();
            //app.UseSession();
            app.UseAuthentication();
            app.UseMvc(routes =>
            {
                routes.MapRoute(
                    name: "default",
                    template: "{controller=Home}/{action=Index}/{id?}");

                routes.MapSpaFallbackRoute(
                    name: "spa-fallback",
                    defaults: new { controller = "Home", action = "Index" });
            });

        }

screenshot of trying to navigate to main-client.js

enter image description here

like image 843
Rilcon42 Avatar asked Sep 21 '18 18:09

Rilcon42


People also ask

What C is used for?

C programming language is a machine-independent programming language that is mainly used to create many types of applications and operating systems such as Windows, and other complicated programs such as the Oracle database, Git, Python interpreter, and games and is considered a programming foundation in the process of ...

Is C language easy?

Compared to other languages—like Java, PHP, or C#—C is a relatively simple language to learn for anyone just starting to learn computer programming because of its limited number of keywords.

What is C in C language?

What is C? C is a general-purpose programming language created by Dennis Ritchie at the Bell Laboratories in 1972. It is a very popular language, despite being old. C is strongly associated with UNIX, as it was developed to write the UNIX operating system.

What is the full name of C?

In the real sense it has no meaning or full form. It was developed by Dennis Ritchie and Ken Thompson at AT&T bell Lab. First, they used to call it as B language then later they made some improvement into it and renamed it as C and its superscript as C++ which was invented by Dr.


1 Answers

First, a small note: I faced the exact same problem, but using ASP.NET MVC 5. So, I cannot guarantee 100% that the same exact solution is going to work.

But, I'm fairly sure that the technique I'm going to describe is sound, and at the very least can be adapted with a minimal effort.

Coming to the root of the problem: in order to have ASP.NET MVC (MVC for short) and Angular 2+ happily coexist, they have to know where the routing handling responsibility is going to end for the server and where it does start for the client.

We have also to understand how MVC simplifies addresses when fed with a routing request.

The solution I advocate is in creating a single Controller, called NgController, which is going to serve all the Single Page Applications (from now on, SPAs) in your installation.

A sketch of NgController is this:

public class NgController : Controller
{
    private boolean Authenticated()
    {
        // returns true if the user is authenticated. This system can
        // (and probably should) be integrated or replaced with
        // one of the ASP.NET Core authentication modules.
        return true;
    }

    public ActionResult Index()
    {
        if (!Authenticated())
            return RedirectToAction("Login", "Authentication", new { ReturnUrl = HttpContext?.Request?.Path });

        // Index does not serve directly the app: redirect to default one
        return RedirectToAction("TheApp");
    }

    // One action for each SPA in your installment
    public ActionResult TheApp() => UiSinglePageApp("TheApp");
    public ActionResult AnotherSPA() => UiSinglePageApp("AnotherSPA");

    // The implementation of every SPA action is reusable
    private ActionResult UiSinglePageApp(string appName)
    {
        if (!Authenticated())
            return RedirectToAction("Login", "Authentication", new { ReturnUrl = HttpContext?.Request?.Path });

        return View("Ui", new SinglePageAppModel { AppName = appName });
    }
}

Really basic stuff: we just need to make sure that the default Index action does NOT serve directly any app.

Then, we need to make sure that the MVC routing works properly:

  1. is going to map /TheApp/... and /AnotherSPA/ as root routes (because we like short mnemonic URLs)
  2. is not going to mess with the part of the routing that will be handled by Angular.

Some routes and how they should behave:

  • an URL of https://server:port should redirect to https://server:port/TheApp
  • https://server:port/TheApp should be served by the TheApp action in the NgController
  • https://server:port/AnotherSPA should be served by the AnotherSPA action in the NgController
  • https://server:port/TheApp/some/token/and/subroute/for/angular/deep/linking should be served by the TheApp action in the NgController AND everything after TheApp should be kept as is and go straight to Angular Router

My choice of configuration is this (MVC 5, needs some adaptation for MVC Core 2):

public static void RegisterRoutes(RouteCollection routes)
{
    // the Ng controller is published directly onto the root,
    // the * before id makes sure that everything after the action name
    // is kept as-is
    routes.MapRoute("Ng",
        "{action}/{*id}",
        new {controller = "Ng", id = UrlParameter.Optional},
        new {action = GetUiConstraint()}
    );

    // this route allows the mapping of the default site route
    routes.MapRoute(
        "Default",
        "{controller}/{action}/{*id}",
        new { controller = "Ng", action = "Index", id = UrlParameter.Optional }
    );
}

/// <summary> The Ui constraint is to be used as a constraint for the Ng route.
/// It is an expression of the form "^(app1)|(app2)$", with one branch for
/// each possible SPA. </summary>
/// <returns></returns>
public static string GetUiConstraint()
{
    // If you don't need this level of indirection, just
    // swap this function with the result.
    // return "^(TheApp)|(AnotherSPA)$";

    var uiActions = new ReflectedControllerDescriptor(typeof(NgController))
        .GetCanonicalActions()
        .Select(x => x.ActionName.ToLowerInvariant())
        .Where(x => x != "index")
        .Select(x => "(" + x + ")");

    return string.Concat("^", string.Join("|", uiActions), "$");
}

Now the MVC routing and Controller are fine and testable for the Redirection part and for the "do-not-mess-with-everything-after-the-app-name" part. ;)

We must now setup the only bit of MVC View we need to host the Angular app. We need a single "Ui" view for the "Ng" controller. The only truly important bit of the view is the <base> tag in the head of the html document. I personally copy the dist folder into a static folder inside the MVC site, just to clarify the naming. The "static" folder is meant to contain all the (you guessed it) static content. I think that MVC creates a Content folder, by default, but I never liked that name.

MVCroot
 |-static
   |-ng
     |-TheApp (this is the dist folder for TheApp)
     |-AnotherSPA (this is the dist folder for AnotherSPA)

I also pack every js file into one single file named like the SPA, but it's not necessary.
And I don't like partials, just because I don't need layouts, so I keep everything together.
YMMV.

Ui.cshtml

@{
    Layout = null;
    var baseUrl = Url.Content("/") + Model.AppName;
    var appName = Model.AppName.ToLowerInvariant();
}
<!DOCTYPE html>
<html style="min-height: 100%;">

<head>
    <meta charset="utf-8">
    <meta name="viewport" content="width=device-width, initial-scale=1.0">
    <link rel="icon" type="image/x-icon" href="~/static/favicon.ico">

    <title>My marvelous application</title>

    @Url.CssImportContent("static/ng/{appName}/css/main.css")

    <!-- This is the CRUCIAL bit.  -->
    <base href="@baseUrl">
</head>

<body>
    <app-root>Loading...</app-root>
    @Url.ScriptImportContent($"~static/ng/{appName}/main.js")
</body>

</html>

AAAAAnd... we are done with the MVC side.

On the client side, we just need to ensure Angular manages the routing using the appropriate defaults (do NOT enable the hash option).

And everything should just work.

Hope it helps.

like image 68
A. Chiesa Avatar answered Sep 30 '22 18:09

A. Chiesa