Angular 2 routing with ASP .Net Core (non-mvc) - asp.net

I'm trying to set up routing with .net core and Angular 2 but the routes do not resolve because they are resolved by the server.
One solution I have seen is to register a default route to your home controller or something... but I don't have any MVC controllers.
I've added this to my main component (and done all the other router prerequisites)
#RouteConfig([
{ path: '/', name: 'Table', component: TableComp, useAsDefault: true },
{ path: '/login', name: 'Login', component: LoginComp }
])
And I do have these in startup.cs:
within ConfigureServices()
services.AddMvc();
within Configure()
app.UseMvc();
But since I'm not actually using any MVC Controllers or registering any MVC routes, I'm at a loss as to how to get my angular routes to resolve in the browser rather than the server, and why they aren't just doing the thing...

The following configuration should fit most projects using client side routing in .NET Core:
DefaultFilesOptions options = new DefaultFilesOptions();
options.DefaultFileNames.Clear();
options.DefaultFileNames.Add("index.html");
app.Use(async (context, next) =>
{
await next();
if (context.Response.StatusCode == 404 && !Path.HasExtension(context.Request.Path.Value))
{
context.Request.Path = "/index.html";
await next();
}
})
.UseCors("AllowAll")
.UseMvc()
.UseDefaultFiles(options)
.UseStaticFiles();

You are looking for the Microsoft.AspNetCore.SpaServices found in this github repo. Look for this example.
app.UseStaticFiles();
app.UseMvc(routes =>
{
routes.MapRoute(
name: "default",
template: "{controller=Home}/{action=Index}/{id?}");
routes.MapSpaFallbackRoute(
name: "spa-fallback",
defaults: new { controller = "Home", action = "Index" });
});
There is an older post here with some previous versions, but the setup should be very similar.

Try this example Asp Core + Angular2 + Swashbuckle + Docker.
It uses UseMvc() for C# API controllers. And UseStaticFiles() to serve AngularJs (and other static) files.
So you're running Asp Core backend as service. Using Webpack you can build AngularJs application from Typescript source code. It will be published to public folder Backend looks to serve statics from.

I put index.html in wwwroot and use DefaultFiles() in start up page. The webserver knows and finds the default file - index.html automatically.
public void Configure(IApplicationBuilder app, IHostingEnvironment env, ILoggerFactory loggerFactory)
{
app.UseDefaultFiles();
app.UseStaticFiles();
app.UseMvc();
}

Related

Can i add a static name inside the url for the root site inside my asp.net mvc core

I am working on an asp.net mvc core web application, and currently when i access the root url it will be https://localhost:44363/ which represents https://localhost:44363/home/index. but my question is if i can add a static name (our company name )inside the url for the root, so it will be https://localhost:44363/OurCompnayName/home/index ? can i modify the routing to achieve this or bu suing other techniques ?
public void Configure(IApplicationBuilder app, IWebHostEnvironment env) {
app.UseRouting();
app.UseEndpoints(endpoints => {
//http://localhost:5000/shop -> Products.Index()
//http://localhost:5000/shop/newest -> Products.Newest()
endpoints.MapControllerRoute(
name: "shop",
pattern: "shop/{action=Index}",
defaults: new { controller = "Products" });
//http://localhost:5000/company -> Home.Index()
endpoints.MapControllerRoute(
name: "company",
pattern: "company/{controller=Home}/{action=Index}/{id?}");
//http://localhost:5000 -> Home.Index()
//You should allow default route just in case. Otherwise the base url returns 404
endpoints.MapControllerRoute(
name: "default",
pattern: "{controller=Home}/{action=Index}/{id?}");
});
}
Routing to controller actions in ASP.NET Core

How to detect page refresh in .net core angular SPA template?

I have project setup with .net core MVC SPA template. When the application is loaded, it loads angular spa into MVC.
My startup configure has below code to load SPA.
app.UseSpa(spa =>
{
// To learn more about options for serving an Angular SPA from ASP.NET Core,
// see https://go.microsoft.com/fwlink/?linkid=864501
spa.Options.SourcePath = "ClientApp";
if (env.IsDevelopment())
{
spa.UseAngularCliServer(npmScript: "start");
}
});
Once the SPA is loaded, there is no contact with server side on the MVC site. However when the page is reloaded, I would like to intercept this call and do something with the HTTPRequest and HTTPResponse. How do I achieve this ? I do not have any controller in the MVC project.
My project structure looks like this.
WEB
- wwwroot
- ClientApp ---> Angular spa
- Controllers ---> Empty
- Pages ---> Empty
- startup.cs
- program.cs
For intercepting the request between client and server, you could try ASP.NET Core Middleware. All the requests from client will be handled by middleware.
A simple code like below:
app.Use((context, next) =>
{
Console.WriteLine(context.Request.Path);
return next.Invoke();
});
app.UseSpa(spa =>
{
// To learn more about options for serving an Angular SPA from ASP.NET Core,
// see https://go.microsoft.com/fwlink/?linkid=864501
spa.Options.SourcePath = "ClientApp";
if (env.IsDevelopment())
{
spa.UseAngularCliServer(npmScript: "start");
}
});
Update
app.Map("/css/site1.css", map => {
map.Run(context => {
context.Response.Redirect("/css/site2.css");
return Task.CompletedTask;
});
});

Can't read swagger JSON file on ASP.NET Core 1.2 Application after hosting into local IIS

After hosting my asp.net core 1.2 application, I am getting an error as:
swagger is unable to find the swagger.json file.
I have tried to solve the problem by giving a virtual path name app.UseSwaggerUI() but it's not working.
Edit to clarify question based on comments:
After hosting Asp.net core application in IIS, the swagger.json file is generating on localhost:<random_port>/swagger/v1/swagger.json path.
How do I serve the swagger.json file on a custom route like:
localhost:<random_port>/virtualpathname/swagger/v1/swagger.json
I have tried to set a virtual path in app.UseSwaggerUI() like {virtualpathname}/swagger/v2/swagger.json but still it is not working
Could be a few reasons for this - one being that .Net Core doesnt serve static files by default (although looking at online examples this doesnt seem to be an issue).
If you havent already, try installing the package Microsoft.AspNetCore.StaticFiles and adding UseStaticFiles() in your Configure() method in Startup.cs with the following configuration. I dont think that the order is important, but this is the order I have mine running in a working app.
public void Configure(...)
{
// Enable middleware to serve static files (like .json)
app.UseStaticFiles();
//Enable middleware for your API
app.UseMvc();
// Enable middleware to serve generated Swagger as a JSON endpoint
app.UseSwagger();
// Enable middleware to serve swagger-ui assets (HTML, JS, CSS etc.)
app.UseSwaggerUI(c =>
{
c.SwaggerEndpoint("/swagger/v1/swagger.json", "YourApp API V1");
});
}
You will also need SwaggerGen middleware configured in your ConfigureServices() method.
services.AddSwaggerGen(c =>
{
c.SwaggerDoc("v1", new Info { Title = "api_name", Version = "1.0"});
});
Edit Based on comment - to serve swagger json on a custom route:
// Enable middleware to serve generated Swagger as a JSON endpoint on a custom endpoint
app.UseSwagger(c => c.RouteTemplate = "custom/swagger/{documentName}/swagger.json");
// Enable middleware to serve swagger-ui assets (HTML, JS, CSS etc.)
// Using custom endpoint defined above
app.UseSwaggerUI(c =>
{
c.SwaggerEndpoint("/custom/swagger/v1/swagger.json", "YourApp API V1");
});
If you need to serve SwaggerUI on a custom route as well, then:
// Enable middleware to serve swagger-ui assets (HTML, JS, CSS etc.)
// Using custom endpoint defined above
// And serving UI on a custom route
app.UseSwaggerUI(c =>
{
c.SwaggerEndpoint("/custom/swagger/v1/swagger.json", "YourApp API V1");
c.RoutePrefix = "custom"; // serves UI on http://{domain}:{port}/custom/
});
I suggest you to perform the two next steps.
First, open your project web.config and enable stdoutLogEnabled. (Remember to create the folder logs on your application folder and give it proper permissions)
Second, make sure you're doing the right configuration. (https://learn.microsoft.com/en-us/aspnet/core/tutorials/web-api-help-pages-using-swagger)
Note: The first step is going to give you more details about the error you're facing.
In my case the issue was the virtual directory which I fixed by adding a relative path(../). In any case make sure you setup ConfigureServices first, then when Configure make sure everything is in order, UseSwagger should be before UseMvc and at the end UseSwaggerUI
public void ConfigureServices(IServiceCollection services)
{
// Add framework services.
services.AddMvc();
services.AddSwaggerGen(c => {
c.SwaggerDoc("v1", new Swashbuckle.AspNetCore.Swagger.Info { Title = "Utility", Version = "v1" });
});
// initialize configuration
var conf = new ConfigurationHelper(Microsoft.DotNet.PlatformAbstractions.ApplicationEnvironment.ApplicationBasePath);
Configuration = conf.Configuration; // just in case
// inject the RestApiWrapperService as singleton into the services configuration
var restService = new RestApiWrapperService(conf);
services.AddSingleton<IRestApiWrapperService>(restService);
}
public void Configure(IApplicationBuilder app, IHostingEnvironment env, ILoggerFactory loggerFactory)
{
loggerFactory.AddConsole(Configuration.GetSection("Logging"));
loggerFactory.AddDebug();
app.UseSwagger();
if (env.IsDevelopment())
{
app.UseDeveloperExceptionPage();
app.UseBrowserLink();
}
else
{
app.UseExceptionHandler("/Home/Error");
}
app.UseStaticFiles();
// app.UseMvc();
app.UseMvc(routes =>
{
routes.MapRoute(
name: "default",
template: "{controller=Home}/{action=Index}/{id?}");
});
app.UseSwaggerUI(s => {
s.RoutePrefix = "help";
s.SwaggerEndpoint("../swagger/v1/swagger.json", "Utility");
s.InjectStylesheet("../css/swagger.min.css");
});
Change the following on your startup.cs class:
app.UseSwaggerUI(c =>
{
c.SwaggerEndpoint("/swagger/v1/swagger.json", "MyService.API v1");
});
To
app.UseSwaggerUI(c =>
{
c.SwaggerEndpoint("/MyWebsiteName/swagger/v1/swagger.json",
"MyService.API v1");
});
[MyWebsiteName] being the name of application configured in IIS.
I happened to have a simple copy paste mistake!
see the first line in below code, the if statement env.IsDevelopment() is causing this section to not run when deployed to IIS. One option is to comment it out!
//if (env.IsDevelopment())
{
app.UseDeveloperExceptionPage();
app.UseSwagger(c =>
{
c.RouteTemplate = "swagger/{documentName}/swagger.json";
});
app.UseSwaggerUI(c => {
c.RoutePrefix = "swagger";
c.SwaggerEndpoint("/swagger/v1/swagger.json", "StockOps.WebAPI v1");
});
}

ASP.NET Core seperate middleware for area controllers

I am building a prototype whereby I host my ASP.NET Core website (standard controllers/views etc) and the API within the same project.
I wish to use the following route scheme:
blah.com/xxxx - website controllers and actions.
blah.com/api/xxxx - api controllers and actions.
My approach thus far is to look at areas and the route config below works perfectly:
app.UseMvc(routes =>
{
routes.MapRoute(
name: "areaDefault",
template: "{area:exists}/{controller=Values}/{action=Index}");
routes.MapRoute(
name: "default",
template: "{controller=Home}/{action=Index}/{id?}");
});
Now I want to expand this to use one set of middleware when accessing the API and another set when accessing the website. In reality this is to use different authentication setups between the areas. Google-fu lead me to the IApplicationBuilder.Map method and this is where my problem lies.
The config below works for the default route, Home/Index is executed but anything after /api returns 404.
// TODO: Add common middleware.
app.Map("/api", builder =>
{
// TODO: Add api specific middleware.
app.UseMvc(routes =>
{
routes.MapRoute(
name: "apiDefault",
template: "api/{controller=Values}/{action=Index}");
});
});
// TODO: Add website specific middleware.
app.UseMvc(routes =>
{
routes.MapRoute(
name: "default",
template: "{controller=Home}/{action=Index}/{id?}");
});
Routes i have tried without success are:
foo.com/api
foo.com/api/values
foo.com/api/values/index
foo.com/api/api
foo.com/api/api/values
foo.com/api/api/values/index
The ValuesController is under the folder Areas/Api/Controllers and is defined as:
[Area("api")]
public class ValuesController : Controller
{
public IActionResult Index()
{
return Json(new { test = 1 });
}
}
The full source to reproduce this is available here: https://github.com/AntSwift/ApiRouteTest
Am I heading down the right path with this, is there something obvious I'm missing or is what I am attempting simply not possible.
I suggest to do it differently and don't mix them together. Just create your api area or use api routing the way you want. Then create a middleware and name it "ApiAuthenticationMiddleware". Put it somewhere at the top of the configure pipeline. In that middleware look at the different properties in httpcontext . There are a lot of useful values in httpcontext. In your case you could use "path" property to find out where the request are going.
If the path has "/api/" in it. It means its going to your api's. Otherwise its going to your normal controllers. In that case you should short-circuit to next step in the pipeline.
Again in your case you could use below snippet in your middleware as an example. You should change it the way you want:
public async Task Invoke(HttpContext httpContext, YourDbContext dbContext)
{
string requestPath = httpContext.Request.Path.Value;
if(!requestPath.Contains("/api/"))
await _next.Invoke(httpContext);
string authHeader = httpContext.Request.Headers["Authorization"];
if (authHeader != null && authHeader.StartsWith("Basic"))
{
//Extract credentials
// other stuff
// example when no authorization header exists and want to reject.
httpContext.Response.StatusCode = 401; //Unauthorized
return;
// Call the next delegate/middleware in the pipeline
await _next.Invoke(httpContext);
}
}
Check this, in the map section replace app.UseMvc with builder.UseMvc
app.Map("/api", builder =>
{
// TODO: Add api specific middleware.
app.UseMvc(routes => // should be builder.UseMvc(routes
{
routes.MapRoute(
name: "apiDefault",
template: "api/{controller=Values}/{action=Index}");
});
});

ASP.NET 5 + Angular 2 routing (template page not REloading)

Angular 2 beta uses html5 routing by default.
However, when you go to a component and the route changes (eg http://localhost:5000/aboutus) and you reload/refresh the page, nothing is loaded.
The issue has been raised in this post also.
Most of the answers say that if we are going to pursue HTML5 routing in angular 2, then this issue of routing should be taken care of in server-side. More discussion here.
I am not sure how to handle this issue using the asp.net server environment.
Any angular 2 devs out there who also uses asp.net and encounters this issue?
PS. I'm using ASP.NET 5. My Angular 2 routes are using MVC routes.
The problem you're seeing has to do with the difference between Angular routing on the client and MVC server-side routing. You are actually getting a 404 Page Not Found error because the server does not have a Controller and Action for that route. I suspect you are not handling errors which is why it appears as if nothing happens.
When you reload http://localhost:5000/aboutus or if you were to try to link to that URL directly from a shortcut or by typing it into the address bar (deep linking), it sends a request to the server. ASP.NET MVC will try to resolve that route and in your case it will try to load the aboutusController and run the Index action. Of course, that's not what you want, because your aboutus route is an Angular component.
What you should do is create a way for the ASP.NET MVC router to pass URLs that should be resolved by Angular back to the client.
In your Startup.cs file, in the Configure() method, add an "spa-fallback" route to the existing routes:
app.UseMvc(routes =>
{
routes.MapRoute(
name: "default",
template: "{controller=Home}/{action=Index}/{id?}");
// when the user types in a link handled by client side routing to the address bar
// or refreshes the page, that triggers the server routing. The server should pass
// that onto the client, so Angular can handle the route
routes.MapRoute(
name: "spa-fallback",
template: "{*url}",
defaults: new { controller = "Home", action = "Index" }
);
});
By creating a catch-all route that points to the Controller and View that ultimately loads your Angular app, this will allow URLs that the server does not handle to be passed onto the client for proper routing.
In your Startup.cs add this to the Configure method. This must be before other app statements.
app.Use(async (context, next) => {
await next();
if (context.Response.StatusCode == 404 && !Path.HasExtension(context.Request.Path.Value)) {
context.Request.Path = "/index.html"; // Put your Angular root page here
await next();
}
});
My favorite solution is to add the following code to Global.asax.cs which very smoothly and reliably takes care of the issue:
private const string RootUrl = "~/Home/Index";
// You can replace "~Home/Index" with whatever holds your app selector (<my-app></my-app>)
// such as RootUrl="index.html" or any controller action or browsable route
protected void Application_BeginRequest(Object sender, EventArgs e)
{
// Gets incoming request path
var path = Request.Url.AbsolutePath;
// To allow access to api via url during testing (if you're using api controllers) - you may want to remove this in production unless you wish to grant direct access to api calls from client...
var isApi = path.StartsWith("/api", StringComparison.InvariantCultureIgnoreCase);
// To allow access to my .net MVCController for login
var isAccount = path.StartsWith("/account", StringComparison.InvariantCultureIgnoreCase);
if (isApi || isAccount)
{
return;
}
// Redirects to the RootUrl you specified above if the server can't find anything else
if (!System.IO.File.Exists(Context.Server.MapPath(path)))
Context.RewritePath(RootUrl);
}
You need use this routing in ASP.NET MVC
app.UseMvc(routes =>
{
routes.MapRoute("Default", "{*url}", new { #controller = "App", #action = "Index" });
});
Then you need set up SystemJS with basePath options
The feature you're looking for is URL rewrite. There are two possible ways to handle it. The classic way is to let IIS do the work, as described here:
https://stackoverflow.com/a/25955654/3207433
If you don't want to depend on IIS, you can instead handle this in the ASP.NET 5 middleware, as shown in my answer here:
https://stackoverflow.com/a/34882405/3207433
I'm not having any luck getting
routes.MapRoute("Default", "{*url}",
new { #controller = "App", #action = "RedirectIndex" });
to work. I still get a 404 with any client side route.
Update:
Figured out why the catch-all route wasn't working: I had an attribute route defined ([Route("api/RedirectIndex")]) and while the plain route can be directly accessed with the fallback route it didn't fire. Removing the attribute route made it work.
Another solution that seems to work just as easy as the catch-all route handler is to just create a custom handler that fires at the end of the middleware pipeline in Configure():
app.UseMvc(routes =>
{
routes.MapRoute(
name: "default",
template: "{controller=Home}/{action=Index}/{id?}");
});
//handle client side routes
app.Run( async (context) =>
{
context.Response.ContentType = "text/html";
await context.Response.SendFileAsync(Path.Combine(env.WebRootPath,"index.html"));
});
This basically ends up being the catch-all route that simply sends index.html out over the existing URL request if there was no other handler that picked up the request.
This works nicely even in combination with IIS Rewrite rules (in which case the above just won't ever get fired.
Wrote up a blog post on this topic:
Handling HTML5 Client Route Fallbacks in ASP.NET Core
Here are two more options for solving this problem. You can either add the hash location strategy to your app module.
import { LocationStrategy, HashLocationStrategy } from '#angular/common';
#NgModule({
imports: [.... ],
declarations: [...],
bootstrap: [AppComponent],
providers: [
{
provide: LocationStrategy,
useClass: HashLocationStrategy
}
]
})
export class AppModule { }
This option will only work for the parts of your Angular2 app that live on the Home ASP Controller
Your second option is to add routes to your ASP Controller that match your Angular 2 app routes and return the "Index" View
public class HomeController : Controller
{
public IActionResult Index()
{
return View();
}
[ActionName("Angular-Route1")]
public IActionResult AngularRoute1()
{
return View("Index");
}
public IActionResult Route2()
{
return View("Index");
}
}
Did you use:
directives: [RouterOutlet, RouterLink] in the component.
apply the #ZOXEXIVO's solution then, in your _Layout.cshtml add this:
<head>
<base href="/"/>
.....
</had>
You can use both the routing
when you call Home/Index from angular routing.
write
Home/Index.cshtml
<my-app></my-app>
app.routing.ts
const routes: Routes = [
{ path: '', redirectTo: '/Home/Index', pathMatch: 'full' },
{ path: 'Home/Index', component: DashboardComponent }
]
So When URL will be Home/Index
will load the component of active url so it will load dashboard component.
The above selected solution did not work for me I also got 404 after following all the comments to the T. I am using an angular5 app in an MVC5 app. I use the default index landing page as the start for the angular5. My angular app is in a folder named mvcroot/ClientApp/ but on ng build it puts the distributed files in mvcroot/Dist/ by altering one setting in the .angular-cli.json file with "outDir": "../Dist"
This solution did work though.
This way only routes in the Dist directory get the fall over. Now you can hit refresh every time and exact route for the angular5 app reloads while staying on the correct component. Be sure to put the catch all first. On a side note, if using a token auth in your angular5, save the token to window.localStorage (or some other mechanism outside your angular5 app) as hitting refresh will wipe out all memory where you you maybe storing your token in a global variable. This keeps the user from having to login again if they refresh.
routes.IgnoreRoute("{resource}.axd/{*pathInfo}");
routes.MapRoute(
"Catch All",
"dist/{*url}",
new { controller = "Home", action = "Index", id = UrlParameter.Optional });
routes.MapRoute(
name: "Default",
url: "{controller}/{action}/{id}",
defaults: new { controller = "Home", action = "Index", id = UrlParameter.Optional }
);

Resources