Changing the base URL of a Blazor WASM app - c#

I have a Blazor WebAssembly app which is server hosted.
When I deploy it on an internal work network, the address is just the IP address, e.g. http://192.168.1.23. That works, but it's not so user friendly. I want it to be http://192.168.1.23/myapp.
I've done some reading on this here (Microsoft), and also here (Stackoverflow). They suggest a couple of things:
In Client side code, index.html, change <base href="/" /> to <base href="/myapp/" />.
In Server side code, Startup.cs, add app.UsePathBase("player"); early on in the Configure() method.
Microsoft also says how to edit the launch settings so that debug still works normally, and they include adding the following to the launchSettings.json file:
"commandLineArgs": "--pathbase=/myapp",
"launchUrl": "myapp"
They don't say if this is in the Client or Server launchSettings.json file, and I can't get it to work anyway. Even if I add "launchUrl": "https://www.google.com" everywhere, nothing different happens when I run it.
Ignoring number 3 above though, the app will start, and I can get to the correct place by manually adding /myapp to the address. So far so good.
I am using cookie authentication though, and when I try to retrieve the user details from the user details from the HttpContext using this method on a hub:
public static User GetAuthenticatedUser(this HttpContext context)
{
var idClaim = context.User.Claims.FirstOrDefault(c => c.Type.Equals(ClaimTypes.NameIdentifier));
var nameClaim = context.User.Claims.FirstOrDefault(c => c.Type.Equals(ClaimTypes.Name));
return new User
{
Id = int.Parse(idClaim.Value),
Name = nameClaim.Value
};
}
It fails, with each claim being null. It did not before I set the base path. How can I overcome this issue?
Note: It works on a regular controller, it's specifically a hub it is failing on, so it's as if the hub is not receiving the same HttpContext as before. The hub was also setup in Startup.cs, with the following in the Configure() method:
app.UseEndpoints(endpoints =>
{
endpoints.MapHub<MyHub>("/myhub1");
endpoints.MapRazorPages();
endpoints.MapControllers();
endpoints.MapFallbackToFile("index.html");
});
Any advice is much appreciated.

Related

ASP.NET - How To Return Both A Web Response And A View?

I've got a servers controller with an action along the lines of ...
[ApiController]
[Route("[controller]")]
[Consumes("application/json")]
public class ServersController : Controller
{
public ServersController(IMemoryCache memoryCache) => Cache = memoryCache;
private IMemoryCache Cache { get; }
[HttpGet(Name = "Get Servers")]
public IActionResult GetServers()
=> Ok(Cache.Get<List<ServerGetDTO>>(Context.ServerList));
}
... and I've also got a razor page along the lines of ...
#page "/servers"
<PageTitle>Get Servers</PageTitle>
<h1>Gets The Servers (Only Via Response, For Now)</h1>
I'm adding the razor/blazor stuff ...
builder.Services.AddRazorPages();
builder.Services.AddServerSideBlazor();
app.UseStaticFiles();
app.UseRouting();
app.UseEndpoints(endpoints => endpoints.MapRazorPages());
app.MapBlazorHub();
... but when I navigate to https://.../servers I'm not getting the JSON response that I'm expecting. The expectation is that navigating to the page issues a Get request to the endpoint and then I would both get the view to render on the front-end and also the list of servers as JSON as the response for the Get call.
What am I doing wrong?
UPDATE #1: After having added Blazor on top of my API, I've noticed now that Swagger returns HTML when calling the https://.../servers endpoint, so the HTML document and the JSON response seem to be mutually-exclussive. I'm almost sure I'm missing something, I'll keep investigating.
UPDATE #2: OK, I've figured it out. Now that I know what the problem was, this question, in hindsight, feels silly. This is my first Blazor experiment, and now I know that I've started with a misunderstanding of the technology at an architectural level. Basically, I've bolted onto my already existing API a Blazor Server project, whereas, in fact I should have used a Blazor WASM project instead. Now that I've replaced Blazor Server with Blazor WASM, everything works exactly as expected!
If you want to bolt on a front-end to an already existing API, make sure you're adding Blazor WASM, not Blazor Server.

.net core 3.1 call to User Secrets for SendGrid keys returns null values

I have set up a project following https://learn.microsoft.com/en-us/aspnet/core/security/authentication/accconfirm?view=aspnetcore-3.1&tabs=visual-studio
From Startup.cs:
services.AddTransient<IEmailSender, EmailSender>();
services.Configure<AuthMessageSenderOptions>(Configuration);
_secretOne = Configuration["SecretStuff:SecretOne"];
the _secretOne variable was added to prove that the correct secrets.json file is being accessed. It has both a SecretStuff block and a AuthMessageSenderOptions block.
In EmailSender.cs
public EmailSender(IOptions<AuthMessageSenderOptions> optionsAccessor)
{
Options = optionsAccessor.Value;
}
A breakpoint after Options = shows the keys with null values.
Eventually I gave up and hard coded the Options.SendGridKey and Options.SendGridUser and with this change the project works as it should.
This is my first use of User-Secrets so when it did not work I set up a console app that references the same secrets.json file and it sends emails.
I found a possible answer in: https://www.twilio.com/blog/2018/05/user-secrets-in-a-net-core-web-app.html
I changed Startup.cs to:
services.Configure<AuthMessageSenderOptions>(Configuration.GetSection("AuthMessageSenderOptions"));
and now the values are as they should be. I sure wasted a lot of time looking in the wrong places for an answer.

Enable CORS from a specific origin on Topshelf hosted service

I'm sure this is a duplicate but none of the answers I've found appears to be working.
I have a web.api hosted as a windows service and I need to allow CORS to two specific origins.
I have this in my startup.cs
config.EnableCors(
new EnableCorsAttribute("http://customer.mydomain.com,
http://admin.mydomain.com", "*", "*")
);
var policy = new CorsPolicy()
{
AllowAnyHeader = true,
AllowAnyMethod = true,
SupportsCredentials = true
};
policy.Origins.Add("*http://customer.mydomain.com");
policy.Origins.Add("*http://admin.mydomain.com");
app.UseCors(new CorsOptions
{
PolicyProvider = new CorsPolicyProvider
{
PolicyResolver = context => Task.FromResult(policy)
}
});
So my understanding here is that I'll only allow access to my service from admin and customer subdomains?
and then on top of my controller I have
[EnableCors("*.mydomain.com","*","*")]
public class MasterDataController
everything builds and is happy but when I run my service and call a method in the controller directly from the browser URL it responds fine, shouldn't it be blocking me as I'm not calling it from mydomain.com ?
I've seen answers saying I need to send my origin in my call but surely that's not the answer as I specifically want to stop calls unless they are from my allowed origins? I feel like I'm going in circles here
[Update]
I've also added
<httpProtocol>
<customHeaders>
<add name="Access-Control-Allow-Origin" value="*.bizcash.co.za" />
</customHeaders>
to my IIS proxy and if I check the response headers when I call the method directly I see the following
*.redacted obviously being my domain. However, I can still call it directly from my browser and it shouldn't allow that should it? It should only allow the calls if made from my domain?
okay, so two problems with the question.
adding specific CORS origins doesn't stop you calling the service directly in the browser, it stops other web applications calling it. So I was testing it wrong.
So you need to make your changes, deploy it to your server and then test it via your normal applications.
to make it work this is what I have in the end
var policy = new CorsPolicy()
{
AllowAnyHeader = true,
AllowAnyMethod = true
};
policy.Origins.Add("https://customer.mydomain.com");
policy.Origins.Add("https://admin.mydomain.com");
app.UseCors(new CorsOptions
{
PolicyProvider = new CorsPolicyProvider
{
PolicyResolver = context => Task.FromResult(policy)
}
});
That's it, just that in the startup.cs and it works. Hopefully I save someone else two wasted days of life

signalR change default site in startup

I have an application where i have 2 instances of it running on the same IIS server.
/Site1
/Site2
same source code in both, just different settings. I have signalR code on both sites, but i would like Site2 to use the signalR server from Site1.
I have this working so that if changes are made on Site1, Site2 knows to refresh, and that works fine. THE PROBLEM is that if server changes (not javascript) are made on Site2, Site1 doesn't get them. Below is my code:
Startup.cs
app.Map("/signalr", map =>
{
map.UseCors(CorsOptions.AllowAll);
var hubConfiguration = new HubConfiguration
{
#if DEBUG
EnableDetailedErrors = true
#endif
};
map.RunSignalR(hubConfiguration);
});
javascript code:
$.connection.hub.url = "http://servername/Site1/signalr";
...$.connection.start etc
All of that seems to work fine, but what i can't find anywhere is how to push settings in the C# code from Site2 to Site1.
random c# code:
var hub = GlobalHost.ConnectionManager.GetHubContext<myHub>();
this globalhost.connectionmanager.gethubcontext is always pointing to itself. How can i get Site2 to point to Site1 in the last section? I've tried looking into editing Startup.cs to use MapSignalR(..path,...), but the path doesn't take absolute URLs, it has to start with '/', so it won't go up a level.
Any ideas or am i missing something basic?
Apparently what i'm looking for is a backplane. https://learn.microsoft.com/en-us/aspnet/signalr/overview/performance/scaleout-in-signalr . super easy to set up, and fixed my problem immediately (without any of the javascript changes above needed).

SignalR 404 error when trying to negotiate

So I am very new to SignalR, in fact I've only been using it for a couple of days now. Anyway, I am getting the error below when my application first starts up:
The code for the application in question is located in two projects, a Web API and a Single Page Application (SPA). The first one has my backend code (C#) and the second one my client-side code (AngularJS). I think the problem might be due to the fact that the projects in question run on different ports. The Web API, where my SignalR hub lives, is on port 60161 and the SPA is on 60813. My hub is declared like so:
public class ReportHub : Hub
{
public void SendReportProgress(IList<ReportProgress> reportProgress)
{
this.Clients.All.broadcastReportProgress(reportProgress);
}
public override Task OnConnected()
{
this.Clients.All.newConnection();
return base.OnConnected();
}
}
and then in my Startup.cs file for my Web API I initialize SignalR like this:
public void Configuration(IAppBuilder app)
{
HttpConfiguration config = new HttpConfiguration();
config.Services.Replace(typeof(IHttpControllerActivator), new NinjectFactory());
config.MessageHandlers.Add(new MessageHandler());
//set up OAuth and Cors
this.ConfigureOAuth(app);
config.EnableCors();
config.IncludeErrorDetailPolicy = IncludeErrorDetailPolicy.Always;
// Setting up SignalR
app.Map("/signalr", map =>
{
map.UseCors(CorsOptions.AllowAll);
map.RunSignalR(new HubConfiguration { EnableJSONP = true });
});
//set up json formatters
FormatterConfig.RegisterFormatters(config.Formatters);
WebApiConfig.Register(config);
app.UseWebApi(config);
}
For my client-side code I use an Angular SignalR API called angular-signalr-hub (Angular-signalr-hub). The client-side follows:
angular
.module("mainApp")
.factory("reportHubService", ["$rootScope", "Hub", reportHubService]);
/// The factory function
function reportHubService($rootScope, Hub) {
var vm = this;
vm.reportName = "None";
// Setting up the SignalR hub
var hub = new Hub("reportHub", {
listeners: {
'newConnection': function(id) {
vm.reportName = "SignalR connected!";
$rootScope.$apply();
},
'broadcastReportProgress': function (reportProgress) {
vm.reportName = reportProgress.reportName;
$rootScope.$apply();
}
},
errorHandler: function(error) {
},
hubDisconnected: function () {
if (hub.connection.lastError) {
hub.connection.start();
}
},
transport: 'webSockets',
logging: true
//rootPath: 'http://localhost:60161/signalr'
});
I did some googling yesterday and one of the suggestions I came upon was to set the SignalR URL to the one of my Web API, which I did (the commented out line above). When I uncomment the line in question, that does seem to do something because if I now go to http://localhost:60161/signalr/hubs in my browser, it does show me the dynamically generated proxy file:
and when I run my application I no longer get the error above, but now it doesn't seem to connect. It gets to the negotiate line and it stops there:
I think it should look like this (this is from a SignalR tutorial I found):
In addition, none of my listeners (declared in my Angular code above) get called, so something is still now working quite right. There should be more lines in the log to the effect that connection was successfully established, etc. What could be the problem here?
UPDATE: upon further debugging i found out the problem is most likely being caused by the ProtocolVersion property being different between the client and the result here:
Because of that it seems it just exists and fails to establish connection.
I figured out what the problem was. My SignalR dependencies were out of date and because of that my client and server versions differed. All I had to do was update (via NuGet Package Manager) all SignalR dependencies to the latest version and now it works.
As a side note, SignalR was not very good at telling me what was wrong. In fact, no error message was displayed, unless of course there was some additional logging somewhere that had to be found or turned on, in addition to the logging I already had (turned on). Either way, it's either not logging certain errors or it makes it difficult to figure out how to turn on all logging. I had to go and debug the JQuery SignalR api to figure out what the problem was, which was a time consuming endeavour.

Categories

Resources