HostingEnvironment.MapPath does not work with Owin self-hosting option - asp.net

I'm trying to load a file on a self-hosted Owin based server.
WebApp.Start<Startup>("http://localhost:3001/");
Here is how I map the path:
var path = HostingEnvironment.MapPath("~");
it always returns null!
On the other hand, if the website is hosted in IIS (or express) the value of path is right.
How can I can populate this value for the Self-Hosted owin?

As owin self-hosting doesn't use iis, instead of virtual path, the absolute path should be used.
var path = HostingEnvironment.MapPath("~");
if (path == null)
{
var uriPath = Path.GetDirectoryName(Assembly.GetExecutingAssembly().GetName().CodeBase);
path = new Uri(uriPath).LocalPath;
}

When running under OWIN under dnx, I found that many of the suggested workarounds around reading the current app domain would return the .dnx runttime folder - not the web app location.
Here's what worked for me inside OWIN startup:
public void Configure(IApplicationBuilder app)
{
app.Use(async (ctx, next) =>
{
var hostingEnvironment = app.ApplicationServices.GetService<IHostingEnvironment>();
var realPath = hostingEnvironment.WebRootPath + ctx.Request.Path.Value;
// do something with the real file -path
await next();
});

Related

Azure Storage Static website Hosted Standalone Blazor WASM appsettings

I am hosting my standalone Blazor WASM app in an Azure Storage Account Static website and wondering how to handle switching between development and production API endpoints using settings in appsettings.json/appsettings.staging.json. The documentation I've found talks more about App Service hosted apps.
I cannot get this Blazor.start() method to work.
I must admit I haven't tried the option to inject an IConfiguration and use HttpClient but would like to check if there's a simple method.
I can't claim to have come up with this myself but I cannot find my reference right now. Anyway I ended up adding this method to my Program.cs.
Program.cs
private static async Task ConfigureApiEndpoint(WebAssemblyHostBuilder builder)
{
var http = new HttpClient()
{
BaseAddress = new Uri(builder.HostEnvironment.BaseAddress)
};
string apiEndpoint = String.Empty;
if (builder.HostEnvironment.BaseAddress.Contains("localhost"))
apiEndpoint = "api-endpoint-staging.json";
else if (builder.HostEnvironment.BaseAddress.Contains("mysubdomain.domain.com"))
apiEndpoint = "api-endpoint-staging.json";
else
apiEndpoint = "api-endpoint-production.json";
using var response = await http.GetAsync(apiEndpoint);
using var stream = await response.Content.ReadAsStreamAsync();
builder.Configuration.AddJsonStream(stream);
}
api-endpoint-staging.json
Put this in wwwroot folder. Set as Content build action. Do not copy to output.
{
"SirenApi": {
"BaseAddress": "https://mysubdomain.domain.com/api/"
}
}

Integrating ASP.NET code to Active Directory or LDAP after deploying on Bluemix

I'm working on an ASP.Net project, which needs to be deployed after completion on PaaS, which needs to be BlueMix (It wasn't my choice, It was an order).
In addition I need to use an:
Active Directory or LDAP to the User Authentication and Authorization, integrated with the ASP.Net Project.
The Issues Here Are :
1. I have found an integration to the Active Directory or SSO Services using only Java or Node.js, but in my case I am using ASP.Net
2. I want a solution for how the integration can be done on top of the PaaS between the Active Directory and ASP.Net application.
Depending on which version of ADFS you're using, you should be able to use either OAuth or OIDC middleware to connect from an ASP.NET Core application (assuming you're using ASP.NET Core because you're using Bluemix). If you're using at least ADFS 3.0 (Windows Server 2012+), you can use ASP.NET Core's generic OAuth middleware to connect.
First, create a configuration file to store your ADFS server settings, or modify an existing configuration file (such as appsettings.json).
Sample "adfs-settings.json" file:
{
"ADFS": {
"ClientId": "Your ClientId as set on ADFS server",
"ResourceUrl": "url of this application (ex: http://mywebapp.mybluemix.net)",
"ServerUrl": "url of ADFS (ex: https://dc.your.domain)"
}
}
If you created a new file, such as "adfs-settings.json", for your ADFS configuration, add it to your Configuration object in the constructor of your Startup.cs file.
public Startup(IHostingEnvironment env)
{
var builder = new ConfigurationBuilder()
.SetBasePath(env.ContentRootPath)
.AddJsonFile("adfs-settings.json");
Configuration = builder.Build();
}
In your Configure method of Startup.cs create an OAuthOptions object:
var options = new OAuthOptions();
options.AutomaticChallenge = true;
options.AuthenticationScheme = "ADFS";
Specify the ClientId that you created when configuring this application on your ADFS server by reading it from your Configuration object. The generic OAuth middleware also requires that you provide a ClientSecret here even though that value is not actually used by ADFS 3.0.
options.ClientId = Configuration["ADFS:ClientId"];
options.ClientSecret = "ADFS 3.0 does not support confidential client, but OAuth middleware requires it";
Set the callback url which the ADFS server will redirect to in your application.
options.CallbackPath = new PathString("/signin-adfs");
Now configure the OAuthEvents. OnRedirectToAuthorizationEndpoint defines parameters which are passed to the ADFS authorization endpoint when the application determines that a user needs to be authorized. This will require at least a resource parameter which points to the url of your application. OnCreatingTicket is triggered when the ADFS server has finished authorizing a client and returns a JWT token containing claims data to your application. In this method you'll need to process adding roles and claims to the HttpContext object.
options.Events = new OAuthEvents {
OnRedirectToAuthorizationEndpoint = context =>
{
var parameter = new Dictionary<string, string>
{
["resource"] = Configuration["ADFS:ResourceUrl"]
};
var query = QueryHelpers.AddQueryString(context.RedirectUri, parameter);
context.Response.Redirect(query);
return Task.CompletedTask;
},
OnCreatingTicket = context => {
JwtSecurityTokenHandler tokenHandler = new JwtSecurityTokenHandler();
JwtSecurityToken validatedToken = tokenHandler.ReadJwtToken(context.AccessToken);
IEnumerable<Claim> a = validatedToken.Claims;
foreach (var claim in a)
{
// role claim needs to be mapped to http://schemas.microsoft.com/ws/2008/06/identity/claims/role
// for IsInRole() to work properly
if (claim.Type == "role")
{
context.Identity.AddClaim(new Claim(ClaimTypes.Role, claim.Value));
}
else if (claim.Type == "unique_name")
{
// map name to Identity.Name
context.Identity.AddClaim(new Claim(context.Identity.NameClaimType, claim.Value));
}
else
{
// this is optional, if you want any other specific claims from Active Directory
// this will also include some information about the jwt token such as the issue
// and expire times
context.Identity.AddClaim(new Claim(claim.Type, claim.Value));
}
}
return Task.CompletedTask;
}
};
Next, set the ClaimsIssuer to the ADFS url and set the SignInScheme to CookieAuthenticationDefaults.AuthenticationScheme and configure the AuthorizationEndpoint and TokenEndpoint to the proper endpoints on your ADFS server.
options.ClaimsIssuer = Configuration["ADFS:ServerUrl"];
options.SignInScheme = CookieAuthenticationDefaults.AuthenticationScheme;
options.AuthorizationEndpoint = Configuration["ADFS:ServerUrl"] + "/adfs/oauth2/authorize/";
options.TokenEndpoint = Configuration["ADFS:ServerUrl"] + "/adfs/oauth2/token/";
Finally, add the OAuth middleware using the options you've just created:
app.UseCookieAuthentication(new CookieAuthenticationOptions());
app.UseOAuthAuthentication(options);
Now you should be able to apply the [Authorize] attribute to any controller or action which requires authorization with ADFS. For a complete sample application see this GitHub repo.

Login to the MVC 5 ASP.NET template Web Application fails when moved from the root folder

I used the ASP.NET Web Application template to create a new "Single Page Application" with "Authentication: Individual User Accounts". It will run with the default settings without any problem.
If I don't deploy the application to the root folder of the web server the authentication fails. The culprit is in the app.viewmodel.js file where the following code can be found:
self.addViewModel = function (options) {
var viewItem = new options.factory(self, dataModel),
navigator;
// Add view to AppViewModel.Views enum (for example, app.Views.Home).
self.Views[options.name] = viewItem;
// Add binding member to AppViewModel (for example, app.home);
self[options.bindingMemberName] = ko.computed(function () {
if (!dataModel.getAccessToken()) {
// The following code looks for a fragment in the URL to get the access token which will be
// used to call the protected Web API resource
var fragment = common.getFragment();
if (fragment.access_token) {
// returning with access token, restore old hash, or at least hide token
window.location.hash = fragment.state || '';
dataModel.setAccessToken(fragment.access_token);
} else {
// no token - so bounce to Authorize endpoint in AccountController to sign in or register
window.location = "/Account/Authorize?client_id=web&response_type=token&state=" + encodeURIComponent(window.location.hash);
}
}
return self.Views[options.name];
});
The line where window.location = "/Account..." redirects the browser to an URL offset at the root directory. Unfortunately just hard coding this to the new folder instead (which I would like to avoid anyway) does not solve the problem entirely.
The redirect seems to work at first but behind the scenes in the AccountController.csfile
Authorize()is called which in turn calls AuthenticationManager.SignIn(identity) and somehere there is magic going on. There is a redirect to http://localhost/foo/Account/Login?ReturnUrl=... and we're back where we started.
I am probably missing the obvious. I'd appreciate any pointers.
It's very easy to replicate. Just create a new web app with the default settings and then go into project properties and change the "Project Url" to something like http://localhost:49725/foo which moves the app to a new folder called "foo".
I'm facing same problem, the change I did was:
In the file app.viewmodel.js I added a new parameter (returnUrl):
// no token - so bounce to Authorize endpoint in AccountController to sign in or register
window.location = "/Account/Authorize?client_id=web&response_type=token&state="
+ encodeURIComponent(window.location.hash)
+ "&returnUrl=" + encodeURIComponent(window.location);
In the method ValidateClientRedirectUri of class ApplicationOAuthProvider I read this parameter and set as the return url:
public override Task ValidateClientRedirectUri(OAuthValidateClientRedirectUriContext context)
{
if (context.ClientId == _publicClientId)
{
Uri expectedRootUri;
if (!string.IsNullOrEmpty(context.Request.Query["returnUrl"]))
{
expectedRootUri = new Uri(context.Request.Query["returnUrl"]);
}
else
{
expectedRootUri = new Uri(context.Request.Uri, "/");
}
if (expectedRootUri.AbsoluteUri == context.RedirectUri)
{
context.Validated();
}
else if (context.ClientId == "web")
{
var expectedUri = new Uri(context.Request.Query["returnUrl"]);
context.Validated(expectedUri.AbsoluteUri);
}
}
return Task.FromResult<object>(null);
}
The solution for this is to open the app.viewmodel.js and locate the line that reads
/Account/Authorize?client_id=web&response_type=token&state="
and change it to match the path of your subfolder
/YOUR_SITE/Account/Authorize?client_id=web&response_type=token&state="

SignalR Negotiate 404

I am using SignalR 2.0. Everything works fine when running locally on my VS 2012. But when I publish the site on IIS, it breaks. The site loads but one of the scripts returns 404 Not Found. The script is something like.
https://example.com/signalr/negotiate?xxx
This path doesn't exist indeed. The correct path should be:
https://example.com/private/signalr/negotiate?xxx
Note the part in bold.
Inside the WebSite (https://example.com/) I have another Application (https://example.com/private/). This one is using SignalR.
This seems like a bug in SignalR since the signalr/hubs path is accessible from my private site.
I had a similar problem.
Here is the documentation for configuring the /signalr URL.
However, my solution differed from the docs.
Instead of changing the standard app.MapSignalR(), I changed my client code to use /MyApp/signalr. Here is the code where "MyApp" is the virtual directory of my web application.
var connection = $.hubConnection('/MyApp/signalr', {useDefaultPath: false});
var changesHub = connection.createHubProxy('changesHub');
changesHub.on('userCountChanged', function (count) {
$('#user-count').text(count);
});
connection.start().done(function () {
console.log('Hub has started');
changesHub.invoke('subscribeToChanges', user.id);
});
I tried the other way around (change the MapSignalR to the /signalr path) but this did not work and the negotiation was still routed to /MyApp/signalr/negotiate.
I had the same problem, with an application running in the IIS Default Web Site.
All the Microsoft examples show the hub url with a starting \, and I had copied those examples. But this meant that the signalr routing was from the Default Web Site rather than the application. Removing the leading \ solved it.
So I used endpoints in Startup.cs like:
endpoints.MapHub<MyHub>("myHub");
and hub connections in Javascript like:
var connection = new signalR.HubConnectionBuilder().withUrl("myHub").build();
I had the same issue when web site with signalr is not running as root site. Below solution worked for me. instead of using /signalr, use ../signalr. it will work with any site name folder. no hardcoded name 'MyApp'
var connection = $.hubConnection('../signalr', {useDefaultPath: false});
Had the same issue. web sites running as virtual directories of the root site. For some reason prefixing with ../ as in ../signalr didn't work, but ./signalr did.
My sample code:
function initSR() {
// logs signalr messages
$.connection.hub.logging = true;
// Declare a proxy to reference the hub.
var chat = $.connection.myHub;
$.connection.hub.url = "./signalr";
$.connection.hub.start();
// Create a function that the hub can call to broadcast messages.
chat.client.broadcastMessage = function (message) {
// Process Message, take action upon receipt
alert(message);
};
}
I had the same problem, it is all about CORS, you should add Host URL in CORS config in Startup.cs like this:
services.AddCors(option =>
{
option.AddPolicy("AutomationCors", builder =>
{
builder.AllowAnyMethod()
.AllowAnyHeader()
.WithOrigins("YOUR LOCALHOST URL",
"YOUR HOST URL")
.AllowCredentials();
});
});
I faced the same problem. The mistake i was doing that i was calling the wrong endpoint url like i was mapping the Signal Url in Configure service like /notification but calling [API-Host]/api/notification. Removing the api from url and calling [API-Host]/notification fixed for me.
Probably you added MapSignalR() in your Application (https://example.com/private/).
If you want it on the root, then do the configuration on your WebSite (https://example.com/)
#styfle point me in the right direction the problem can be resolve in a more flexible way injecting BASE_URL (at least in angular 4)
import { Injectable, Inject } from '#angular/core';
import { HubConnection } from '#microsoft/signalr';
import * as signalR from '#microsoft/signalr';
import { Subject, Observable } from 'rxjs';
#Injectable()
export class SignalRService {
private hubConnection: HubConnection;
private message: Subject<any>;
constructor(#Inject('BASE_URL') private baseUrl: string) {
}
public connect() {
this.message = new Subject<any>();
this.hubConnection = new signalR.HubConnectionBuilder()
.withUrl(this.baseUrl+"notification-hub")
.withAutomaticReconnect()
.build();
// ...
}
}

AppHarbor NopCommerce running issue

I uploaded nopcommerce solution to appharbor (using this method Can't build notcommerce project under appharbor) and solution succesfully builded, but I receiving 403 error - Forbidden: Access is denied when trying to open page(Allow write-access to file system is set to true).
Thanks and hope for your help
The problem is that the standard NopCommerce solution contains two Web Projects. AppHarbor only deploys one web project per application, and in this case, we happen to deploy Nop.Admin which is not what you want.
To resolve this, you should take advantage of the AppHarbor solution file convention and create an AppHarbor.sln solution file that only references the Nop.Web project.
We use a wrapper in our base controller to ensure that all of our code is oblivious to appharbor port changing.
First, fix in Webhelper.cs:75
public virtual string GetThisPageUrl(bool includeQueryString, bool useSsl)
{
string url = string.Empty;
if (_httpContext == null)
return url;
if (includeQueryString)
{
string storeHost = GetStoreHost(useSsl);
if (storeHost.EndsWith("/"))
storeHost = storeHost.Substring(0, storeHost.Length - 1);
url = storeHost + _httpContext.Request.RawUrl;
}
else
{
#if DEBUG
var uri = _httpContext.Request.Url;
#else
//Since appharbor changes port number due to multiple servers, we need to ensure port = 80 as in AppHarborRequesWrapper.cs
var uri = new UriBuilder
{
Scheme = _httpContext.Request.Url.Scheme,
Host = _httpContext.Request.Url.Host,
Port = 80,
Path = _httpContext.Request.Url.AbsolutePath,
Fragment = _httpContext.Request.Url.Fragment,
Query = _httpContext.Request.Url.Query.Replace("?", "")
}.Uri;
#endif
url = uri.GetLeftPart(UriPartial.Path);
}
url = url.ToLowerInvariant();
return url;
}
So what we did is simply add files from https://gist.github.com/1158264 into Nop.Core\AppHarbor
and modified base controllers:
nopcommerce\Presentation\Nop.Web\Controllers\BaseNopController.cs
public class BaseNopController : Controller
{
protected override void Initialize(RequestContext requestContext)
{
//Source: https://gist.github.com/1158264
base.Initialize(new RequestContext(new AppHarborHttpContextWrapper(System.Web.HttpContext.Current),
requestContext.RouteData));
}
//Same file from here downwards...
}
nopcommerce\Presentation\Nop.Web.Admin\Controllers\BaseNopController.cs
public class BaseNopController : Controller
{
protected override void Initialize(System.Web.Routing.RequestContext requestContext)
{
//set work context to admin mode
EngineContext.Current.Resolve<IWorkContext>().IsAdmin = true;
//Source: https://gist.github.com/1158264
base.Initialize(new RequestContext(new AppHarborHttpContextWrapper(System.Web.HttpContext.Current), requestContext.RouteData));
//base.Initialize(requestContext);
}
//Same file from here downwards...
}
Enable the Directory Browsing feature in IIS Express
Note This method is for the web developers who experience the issue when they use IIS Express.
To do this, follow these steps:
Open a command prompt, and then go to the IIS Express folder on your computer. For example, go to the following folder in a command prompt:
C:\Program Files\IIS Express
Type the following command, and then press Enter:
appcmd set config /section:directoryBrowse /enabled:true
refrence :https://support.microsoft.com/en-us/kb/942062

Resources