.Netcore api with ReactJs dynamic URL Pathname - c#

I'm currently in the process of converting project frontend from .netcore web application (razor page) to reactjs, one of the road block we encounter is the dynamic url path that we applied in razor page.
As the application is deployed on IIS under default website, so the url is construct as https://localhost/Project. One of the project requirement is it can to be replicate and deploy many time, so there will be https://localhost/Project{n}. That mean the url path name need to be configurable so the frontend JavaScript will request to the correct api url. What i done was config the pathname in iis application's webconfig and inject it into the razor page, so javascript can refer to the injected pathname variable.
But when come to reactjs, it is a seperate project from backend but they share the same url, and reactjs has no way to get the configurable pathname so it can only stick to whatever set in the public_url/homepage, and it can only be set before project is build. As it is not set dynamically, the frontend point to the right default page but wrong javascript url, so it cant even run the react main chunk js.
Any lead for me to continue on? Or i need to change the way of configurable pathname?

In React you can use standard JS. So in order to get the current path name from your React application, you can do something like:
const pathname = location.pathname
If you need the pathname in many different places in your React project, you can set it as a context. (See this documentation)
Alternatively, if you only need it on the initial load, you can just do a useEffect in your app.js file like:
const {pathname, setPathname} = React.useState();
React.useEffect(()=>{
setPathname(location.pathname);
}, [])

Related

Blazor in MVC: Component gets rendered, but #onclick not working. Problem with connection

I'm trying to use Blazor in a .net core 3 MVC project. I used a few tutorials to do this, like https://fizzylogic.nl/2019/08/18/integrating-blazor-in-an-existing-asp-net-core-application/ and https://chrissainty.com/using-blazor-components-in-an-existing-mvc-application/ .
What is working: The initialization of the Blazor component is working. The OnInitializedAsync() function gets triggered and the component gets rendered fine.
This is the call from the MVC View:
#(await Html.RenderComponentAsync<MyProject.Components.Locations>(RenderMode.ServerPrerendered))
What isn't working: using #onclick in the Blazor component. This is some sample code:
<span #onclick="AddLocation"></span>
#code {
private void AddLocation(){
}
}
The reason it probably doesn't work is that I get errors in the initialization of the connection. The component gets rendered not from the base path, but from the url of the specific view, and I think that messes with the connection. I receive these errors in the console of Chrome:
[2019-12-02T09:26:08.117Z] Information: Normalizing '_blazor' to 'https://localhost:44375/Locations/_blazor'.
_blazor/negotiate:1 Failed to load resource: the server responded with a status of 404 ()
blazor.server.js:1
[2019-12-02T09:26:08.155Z] Error: Failed to complete negotiation with the server: Error
e.log # blazor.server.js:1
blazor.server.js:1
[2019-12-02T09:26:08.156Z] Error: Failed to start the connection: Error
e.log # blazor.server.js:1
blazor.server.js:15
[2019-12-02T09:26:08.156Z] Error: Error
e.log # blazor.server.js:15
blazor.server.js:1
-Uncaught (in promise) Error: Cannot send data if the connection is not in the 'Connected' State.
The main fault is probably the 'Normalizing '_blazor' to 'https://localhost:44375/Locations/_blazor''. Requesting _blazor from the base path works. Does anyone know how to set the basepath in such a situation?
(Yes, Blazor in a normal Razor page environment is probably easier, but I want to try Blazor in MVC.)
edit 1
<base href="/" /> in the header seems to have fixed the errors in the console. I hoped it would fix the onclick issue, but not yet... I think the component gets somehow treated like a normal razor component, and not as a blazor component. If I look at the generated html, I can find this: <span #onclick="AddLocation" ></span>, and I think that is wrong (with a normal razor/blazor project, the #onclick dissapears from the html). Anyone knows the solution for this? Tried this: https://stackoverflow.com/a/58444907/1794246 , but that didn't do much.
edit 2
Having a _imports.razor file in the root of the MVC project did obviously nothing. Adding these using statements to the Blazor component actually fixed the problem with the onclick (also Intellisense recognized things in the component a bit better):
#using Microsoft.AspNetCore.Components
#using Microsoft.AspNetCore.Components.Web;
The solution to the problem was changing two things:
To fix the errors in the console I had to put <base href="/" /> in the header of _Layout.cshtml
To fix the actual #onclick issue I had to put these using statements in the Blazor component:
#using Microsoft.AspNetCore.Components
#using Microsoft.AspNetCore.Components.Web;
After that the #onclick was working and intellisense recognized stuff a bit better in the Blazor component.
From the docs: https://learn.microsoft.com/en-us/aspnet/core/host-and-deploy/blazor/index?view=aspnetcore-3.0&tabs=visual-studio#app-base-path
App base path
The app base path is the app's root URL path. Consider the following ASP.NET Core app and Blazor sub-app:
The ASP.NET Core app is named MyApp:
The app physically resides at d:/MyApp.
Requests are received at https://www.contoso.com/{MYAPP RESOURCE}.
A Blazor app named CoolApp is a sub-app of MyApp:
The sub-app physically resides at d:/MyApp/CoolApp.
Requests are received at https://www.contoso.com/CoolApp/{COOLAPP RESOURCE}.
Without specifying additional configuration for CoolApp, the sub-app in this scenario has no knowledge of where it resides on the server. For example, the app can't construct correct relative URLs to its resources without knowing that it resides at the relative URL path /CoolApp/.
To provide configuration for the Blazor app's base path of https://www.contoso.com/CoolApp/, the <base> tag's href attribute is set to the relative root path in the Pages/_Host.cshtml file (Blazor Server) or wwwroot/index.html file (Blazor WebAssembly):
<base href="/CoolApp/">
Blazor Server apps additionally set the server-side base path by calling in the app's request pipeline of Startup.Configure:
app.UsePathBase("/CoolApp");
By providing the relative URL path, a component that isn't in the root directory can construct URLs relative to the app's root path. Components at different levels of the directory structure can build links to other resources at locations throughout the app. The app base path is also used to intercept selected hyperlinks where the href target of the link is within the app base path URI space. The Blazor router handles the internal navigation.
In many hosting scenarios, the relative URL path to the app is the root of the app. In these cases, the app's relative URL base path is a forward slash (<base href="/" />), which is the default configuration for a Blazor app. In other hosting scenarios, such as GitHub Pages and IIS sub-apps, the app base path must be set to the server's relative URL path of the app.
To set the app's base path, update the <base> tag within the <head> tag elements of the Pages/_Host.cshtml file (Blazor Server) or wwwroot/index.html file (Blazor WebAssembly). Set the href attribute value to /{RELATIVE URL PATH}/ (the trailing slash is required), where {RELATIVE URL PATH} is the app's full relative URL path.
For an Blazor WebAssembly app with a non-root relative URL path (for example, <base href="/CoolApp/">), the app fails to find its resources when run locally. To overcome this problem during local development and testing, you can supply a path base argument that matches the href value of the <base> tag at runtime. Don't include a trailing slash. To pass the path base argument when running the app locally, execute the dotnet run command from the app's directory with the --pathbase option:
dotnet run --pathbase=/{RELATIVE URL PATH (no trailing slash)}
For a Blazor WebAssembly app with a relative URL path of /CoolApp/ (<base href="/CoolApp/">), the command is:
dotnet run --pathbase=/CoolApp
The Blazor WebAssembly app responds locally at http://localhost:port/CoolApp.
In my case was passing a specific model as parameter to component, the model has been created with DI and had no parameterless constructor. Once such default constructor was added everything started to work fine.

API Calls to controller is breaking after deploying to IIS

I am invoking API calls to get JSON data from server using javascript.
$(function () {
var customersTable = $('#Customers');
var returnCustomersTable = UpdateDataTable(customersTable, "/Customers/Loaddata");
}
This works fine when I am working with Visual studio Dev environment , because my web application is on root and so all javascripts Works fine.
Example:
My WebSite URL: http://localhost:4391
API Calls will be: http://localhost:4391/Customers/Loaddata
This works fine.
But when I deploy application to IIS, my website URL will be,
My WebSite URL: http://localhost/MyAppName
But API calls would still be,
API Calls will be: http://localhost/Customers/Loaddata, which results in not found.
Since I am using javascript in a separate file , I wont be able to do URL.Action.
The other option would be to create a base URL for dev and prod and the append with each servcie call.
But I am thinking if there is any other way.
The routing configuration in ASP.NET MVC is designed to serve as the single place in the application where all of the URLs can be maintained.
It helps considerably with maintenance of the project if all URLs are generated using routing through one of the UrlHelper based methods, such as Url.Action() rather than hard coded in controllers and views.
As long as your JavaScript is in an MVC view, you can just resolve Url.Action inline.
$(function () {
var customersTable = $('#Customers');
var returnCustomersTable = UpdateDataTable(customersTable,
'#Url.Action("Loaddata", "Customers")');
}
If the JavaScript is in a separate file outside of MVC, consider passing the URL through a variable or function parameter.
This routing feature also makes it easier to deploy ASP.NET MVC, because the generated URLs will adapt to use the application path when the application is not deployed to the root website virtual directory.
Application running in IIS web site root:
/Customers/Loaddata
Application running in virtual subdirectory configured as IIS Application named "MyAppName":
/MyAppName/Customers/Loaddata

Using config vars with angular and JQuery

I have an .NET application that uses mostly AngularJS, but for getting some things to work on IE9 we used sometimes a Jquery plugin to fix it (e.g. file upload)
Then on the other side there is an API that handles that application. But the API and the App won't be running on the same urls, so as good practice I would love to have 1 place where all the URl's can be set for where the API is where you can find the videos, ....
but how can I handle all this?
If i set them on the angular side, the controller is loaded after the JQuery,
so the value is undefined.
It can't read the web.config because it's just plain html files that are getting loaded. so no support from C#.
And didn't find any way on loading it in via JQuery.
Any suggestion on how to do this?
You can set it as a basic Object so scripts outside of Angular can use it. When you need to use it in the app, you can either require it directly or set it as a constant or create a service using it.
For example,
var api = {
baseUrl: 'www.moo.com/api/v1',
dogs: '/dogs',
cows: '/cows'
};
alert('This alert was created prior to angular module declartion with your api: ' + api.baseUrl);
angular.module('myCheeseIsGoodApp', [])
.constant('apiConstant', api)
.controller('TestController', ['$scope', 'apiConstant', function($scope, apiConstant){
$scope.apiConstant = apiConstant;
}]);
Here is a plunkr to check out how it works.http://plnkr.co/edit/fnqcwHxgxgrZlCpryMiC?p=preview
Keep in mind this was just a quick way to show you how to use it, but you may want to make it a bit more elegant.

why do not show alias name in iis?

I create a website by Visual Studio and publish it.
Then in IIS I do:
dd application and
set alias name to CIP and set physical path.
Home page is loaded correctly but other pages have wrong URLs in addressbar.
This is homepage URL: "http://localhost/CIP/Pages/Default.aspx".
When I click on the other link showed below link without CIP(alias name).
"http://localhost/Pages/OperationPersonelProgram.aspx".
You need to have your application make sure that the URLs it's using make sense. This requires some server side code to execute. So, if you're writing an MVC website you would use the UrlHelper class, something like:
<a class="link" href="#Url.Content("~/Pages/HostessPersonalProgram.aspx")">mylink</a>
(The above is Razor syntax, but similar should be possible).
Most of the different "styles" of ASP.Net offer a similar shorthand for obtaining the correct URL, but if all else fails you can always use VirtualPathUtility.ToAbsolute:
var url = VirtualPathUtility.ToAbsolute("~/Pages/HostessPersonalProgram.aspx");

Html 5 manifest is not working by iFrame correctly

I've an application which has several C# projects, as following
CRM.DomainLogic
CRM.Web
Warehouse.DomainLogic
Warehouse.Web
,...
Each web project such as CRM.Web has its own 'html views' and 'js controllers'
and there are several other types of static files.
To make deployment easier I'd like to use Html5 manifest.
So, To make separated deployment across projects possible, I've used iframes.
As a result with changes of CRM.Web, clients will get CRM files, and there is no need to download Warehouse.Web files again!
Steps:
1- I've a Web API method that returns all names of Web assemblies, for example CRM.Web and Warehouse.Web
2- I've another Web API method that gets the assembly name as a parameter and return manifest file content which points to files which are located on that project.
public HttpResponseMessage GetManifestByAssemblyName(String assemblyName)
Codes are omitted here.
response.Content = new StringContent(manifestBody.ToString(), Encoding.UTF8, "text/cache-manifest");
3- at client side I create a new iFrame for each assembly and set the src to another web api method which return html body that it's manifest is assigned to the address of WebAPI method that returns manifest body(GetManifestByAssemblyName)
String result = String.Format
(#"<html manifest='{0}'> </html>", "/api/AppManifest/GetManifestByAssemblyName?assemblyName=" + assemblyName + ".manifest");
response.Content = new StringContent(result, UTF8Encoding.Default, "text/html");
The code of iFrames:
var htmlPageUrl = "/api/AppManifest/GetHtmlContainerForManifestByName?assemblyName=" + name;
var iFrame = document.createElement("iFrame");
iFrame.setAttribute("src", htmlPageUrl);
iFrame.setAttribute("sandbox", "allow-same-origin");
iFrame.setAttribute("seamless", "seamless");
document.body.appendChild(iFrame);
When I run application, it gets assembly names, and then creates iFrames, and each iFrame gets its own manifest automatically.
But the window.applicatioCache.status is 0 which means it has no cache.
When I go resources page, I can see followings:
But when I request one of those files which are cached, the request will not use cache.
I know that there is no reference for my work on the web and this is totally based on my ideas, and I know some security restrictions may appear here, but is there any solution to fix a problem?
Thanks in advance.
Note: You can download sample application source code here.
Then press Ctrl+S to save the file, and read Index.html notes first.
I tried your sample project. I had to change sandbox attribute to allow-scripts because otherwise Chrome was blocking JS execution from the iFrame.
Apart from that, I think your cache is working fine. See below screenshot.
I think the root cause of your confusion is that you're checking the main document's appCache status, which doesn't have a manifest. The manifest is for document loaded in iFrame. So in Chrome, if you switch to the individual iFrame and check status, it shows Idle (1) which means cache is active! See below:
Hope this helps!

Categories