Configure routes in C# server for React.js frontend web application - c#

I have started playing around with react.js as a frontend web app and C# Web API and now I want to figure out how to set up the routes.
The current behavior:
When I run the server the application loads correctly with url:
localhost:port/page1
I can switch between pages in navigation bar easily. The url in browser is switching between localhost:port/page1,
localhost:port/page2 and localhost:port/page3
However if I enter for example localhost:port/page3 directly I will get No HTTP resource was found that matches the request URI localhost:port/page3
Can you please explain why using nav bar in react I can get the url correctly but when I enter it directly it will fail?
RouteConfig.cs (C# WebApi)
public class RouteConfig
{
public static void RegisterRoutes(RouteCollection routes)
{
routes.IgnoreRoute("{resource}.axd/{*pathInfo}");
routes.MapRoute(
name: "Default",
url: "*",
defaults: new { controller = "Home", action = "Index", id = UrlParameter.Optional }
);
}
}
routes.js (react app)
where domu is page1, pacienti is page2 and tagy is page3
import React from 'react'
import { Route, IndexRoute, Redirect, IndexRedirect, hashHistory } from 'react-router'
import Layout from './components/utils/layout';
import Domu from './pages/domu';
import Pacienti from './pages/pacienti';
import Tagy from './pages/tagy';
const routes = (
<Router history={hashHistory}>
<Route history={hashHistory} path="/" component={Layout}>
<IndexRoute component={Domu}/>
<IndexRedirect to="domu" />
<Route path="domu" component={Domu}/>
<Route path="pacienti" component={Pacienti}/>
<Redirect from="*" to="pacienti" />
<Route path="tagy" component={Tagy}/>
</Route>
</Router>
);
export default routes;
header.js
import React from 'react';
import { Navbar, Nav, NavItem } from 'react-bootstrap';
import { LinkContainer } from 'react-router-bootstrap';
export default class Header extends React.Component {
render() {
return (
<Navbar inverse>
<Navbar.Header>
<Navbar.Brand>
<span style={{paddingLeft: '1em'}}>FNO - Urgent</span>
</Navbar.Brand>
<Nav>
<LinkContainer to="/Domu">
<NavItem eventKey={1} href="#/domu">Domu</NavItem>
</LinkContainer>
<LinkContainer to="/Pacienti">
<NavItem eventKey={2} href="#/pacienti">Pacienti</NavItem>
</LinkContainer>
<LinkContainer to="/Tagy">
<NavItem eventKey={2} href="#/tagy">Tagy</NavItem>
</LinkContainer>
</Nav>
</Navbar.Header>
<Nav pullRight>
<NavItem eventKey={1} href="#">Logout</NavItem>
</Nav>
</Navbar>
);
}
}
EDIT:
source codes available here: https://github.com/trannann/reactUrgent

Related

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/tether#1.2.4/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
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:
is going to map /TheApp/... and /AnotherSPA/ as root routes (because we like short mnemonic URLs)
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.

ASP.Net Core/AngularJS: Routing problems showing "/#!/" directory

I'm developing an ASP.Net Core Application that uses AngularJS (version 1). I've got a problem that when I run/launch the application, the home URL always comes out to:
http://localhost:59745/#!/
The specific question I have is towards why it's saying /#!/ and not just the expected root /.
My Startup.cs that declares the MapRoute for the application:
app.UseMvc(config =>
{
config.MapRoute(
name: "Default",
template: "{controller}/{action}/{id?}",
defaults: new { controller = "App", action = "Index" }
);
});
My app.js declaring the angular seeding for the application/module:
var app = angular.module('myScheduler', ['ngStorage', 'ngRoute'])
.config(function ($routeProvider) {
$routeProvider.when("/", {
controller: "homeController",
controllerAs: "vm",
templateUrl: "/views/home.html"
});
$routeProvider.otherwise({
controller: "loginController",
controllerAs: "vm",
templateUrl: "/views/login.html"
});
});
Why is my URL showing the way it is, and how can I default it to just the root / rather than /#!/?

How to delete with ajax and ASP.NET MVC?

I am trying to use ajax to send a delete request to ASP.NET MVC 5 framework.
There is a single page with a single red button.
In the CustomersController :
[HttpDelete]
[Route("^/customers/delete/{id:int}")]
public ActionResult Delete(int id)
{
CarDealerContext ctx = new CarDealerContext();
// Delete customer with given id...
// If I get a get a breakpoint in here I will be a happy camper!
return View();
}
In the RouteConfig :
routes.IgnoreRoute("{resource}.axd/{*pathInfo}");
routes.MapMvcAttributeRoutes();
In the view I only have :
<input id="deleteBtn" class="btn btn-danger" type="button"
value="Delete" data-id="1"/>
This is the script i use when the deleteBtn is clicked :
// The script is loaded after Bootstrap and jQuery.
$("#deleteBtn").on("click", function (event) {
event.preventDefault();
var id = $("#deleteBtn").attr("data-id");
$.ajax({
url: "/customers/delete/" + id,
type: 'delete',
data: { id: id }
});
})
// The request sends http://localhost:61402/customers/delete/1
// and the response is 404...
All I want is to activate the Delete method in the CustomersController.
I have tried pretty much everything in the Route(regex) attribute.
The second I change the Method to [HttpGet] it works.
Would also appreciate a good studying source on the subject.
You need to enable these verbs in IIS (put, delete)
Iisexpress may need config file edit
Change your Action Method as follows:
[HttpGet]
public ActionResult Delete(int id)
{
//Your Code
}
Two points to note:
1) Instead of HTTPDelete, you should set an HTTPGet annotation because the request you're sending from your view is a GET request, not one that's conventionally understood as a delete operation type request.
2) This may not be required in your case but I thought I'd mention it anyhow. ASP.NET MVC prefers convention over configuration. You don't need to explicitly set that route. The default route in the RouteConfig file will point to your method.
This would be the default configuration in the RouteConfig file:
routes.MapRoute(
name: "Default",
url: "{controller}/{action}/{id}",
defaults: new { controller = "Home", action = "Index", id = request to this Action Method (if you don't have it, it's probably a good idea UrlParameter.Optional }
);

urls like "home.asp" redirect in mvc

I need to rebuild a website (in old they use classic ASP but they want to make it now MVC 4) and they dont want to change the urls. for example if the search screen's url is blabla.com/search.asp they want to keep it like this. But mvc doesn't allow to make urls like "search.asp". I want to keep url like this but render search View. Am I need to do this all one by one or there is a dynamic way for it?
for example
requested url = "blabla.com/string variable"
if variable.Substring(variable.Length - 4) == ".asp";
return View("variable.Substring(0, (variable.Length - 4))")
Note: Syntax is all wrong, I know. I just tried to explain the condition..
Note2: They want this because of "SEO" things. they don't want to lose their ratings. any method that doesn't change anything for google, they will accept that method I guess.
You need two things.
Define a route for *.asp
Add an handler for *.asp
RouteConfig
public static void RegisterRoutes(RouteCollection routes)
{
routes.IgnoreRoute("{resource}.axd/{*pathInfo}");
routes.MapRoute(
name: "DefaultAsp",
url: "{controller}/{action}.asp/{id}",
defaults: 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 }
);
}
Handler (WebConfig)
(This one needs to be inserted inside /system.webServer/handlers
<add name="AspFileHandler" path="*.asp" verb="GET" type="System.Web.Handlers.TransferRequestHandler" preCondition="integratedMode,runtimeVersionv4.0" />
Doing this you are also making all URL's built with MVC available with .asp, that means that if you have an anchor that calls another View, that view will have the normal mvc URL with .asp suffix.
Note
This is generic, you only need to add this line once.
Home/Index displayed are just the default Controller and Action.
Note 2
Forgot to explain the handler.
You need it because IIS thinks you are asking for an ASP file an it'll try to reach that page and return an error since it doesn't exist.
With this handler you are allowing your application to handle those pages itself.
MVC does allows you to write a Route containing an extension, and pointing it to a specific Controller:
In RouteConfig.cs:
public static void RegisterRoutes(RouteCollection routes)
{
routes.MapRoute("Test", "test.asp", new {controller = "Test", action = "test" });
}
Then, in your TestController.cs:
public class TestController : Controller
{
public ActionResult Test()
{
var obj = new Foo();
//Do some processing
return View(obj);
}
}
In this way, you can access http://www.foo.com/test.asp without issues, and maintaining the .asp your client requires.

Redirect from .aspx url to .aspx page

I'm updating my asp.net page from asp.net 2.0 to MVC4.
What I'm trying to do is to basically keep supporting the existing url:
mypage.com/one.aspx to be backwards compatible, except that I want to put all aspx pages in a aspx directory, so the new url would be mypage.com/aspx/one.aspx.
If I type the url directly mypage.com/aspx/one.aspx it works and the page comes up.
Now I'm trying to use RouteConfig to tell it to redirect one.aspx to aspx/one.aspx, but I see no way on how to do it. All RouteConfig examples I see use controllers, which is not what I want to do:
routes.MapRoute(name: "ONE", url: "one.aspx", defaults: new { controller = "Home", action = "Index" });
Is there a way to use MapRoute in RouteConfig to redirect to another aspx page? I'm using C# with MVC4.
This must work:
routes.MapPageRoute("aspx-redirection", "{page}.aspx", "~/aspx/{page}.aspx");
There is ...
but it's not directly from MapRoute
routes.MapRoute(
name: "Legacy",
url: "aspx222/{page}.aspx",
defaults: new { controller = "Home", action = "Page", page = UrlParameter.Optional }
);
And after in Home in Page
public ActionResult Page(string page)
{
return Redirect(String.Format("/aspx/{0}.aspx",page));
}

Categories