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
xxx/Home/index
which is simply a View that calls an Angular component (which throws a bunch of 500 errors)Manually routing to Angular
/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:
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
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 ...
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? 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.
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.
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:
/TheApp/...
and /AnotherSPA/
as root routes (because we like short mnemonic URLs)Some routes and how they should behave:
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 RouterMy 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.
If you love us? You can donate to us via Paypal or buy me a coffee so we can maintain and grow! Thank you!
Donate Us With