Self-Hostable and IIS-hostable OWIN project - iis-6

I would like to modify my solution to run under a self-hosted OWIN instance, but I also need it to run under http://localhost when necessary.
How should I structure my Startup class to be recognised by both?
Currently, I have the Web API project set as a Console Application with the project URL as http://localhost:2746 and this is my Startup class:
[assembly: OwinStartup(typeof(Startup))]
namespace Books
{
public class Startup
{
public static void Main(string[] args)
{
const int port = 2746;
var url = $"http://localhost:{port}/";
using (WebApp.Start<Startup>(new StartOptions(url) { ServerFactory = "Microsoft.Owin.Host.HttpListener" }))
{
var client = new HttpClient { BaseAddress = new Uri(url) };
Console.ReadLine();
}
}
public void Configuration(IAppBuilder app)
{
HttpConfiguration httpConfiguration = new HttpConfiguration();
WebApiConfig.Register(httpConfiguration);
app.Use<CustomExceptionMiddleware>().UseWebApi(httpConfiguration);
app.UseFileServer(StaticFileConfig.Build());
}
}
}
I also have this in the web.config:
<add key="owin:AutomaticAppStartup" value="false" />

use two projects:
create a web project for the api controllers. this project can host in iis
create another console project as the self-host, this project need refer the web project, now you can share the WebApiConfig/Controllers to this project
ps: OwinStartup attribute indicate the entrypoint for iis, but i use the traditional global.asax to initial the api config. so that the Startup class can also share the config from WebApiConfig

Related

appSettings when read by microsoft.Extensions.Options IOptions not working after deployment

Project type: asp.net core 2.2 (console application) web api
I'm using Dependency Injection of microsoft.Extensions.Options IOptions to get configuration appsettings.json into the controller. It is working fine on my local development machine. However - after deploying it - the settings are not injected to the controller. I've added a logger to check the environment which the code was deployed to. When investigating the config variable in the controller constructor, I can see that it has a value, but the properties which should be read from the appsettings is null (see in the code)
In the published folder I can see the appsettings.json file with all the relevant settings.
What am I missing here ?
Here is my code:
public class MyController : controllerBase
{
private ILogger<MyController> _logger;
private readonly IOptions<Myconfig> config;
public MyController(IOptions<Myconfig> config, ILogger<MyController> logger)
{
_logger = logger;
if (config == null)
{
logger.LogError("config is not being injected into the controller");
}
else if (config.Value != null) //this is the selected option but config.Value.ApiUri is null although it has value in the appsettings
{
logger.LogInformation($"config.Value.ApiUri:{config.Value.ApiUri}");
}
this.config = config;
....
}
}
In the Startup.cs, Configuring to get the relevant appsetting entry:
public void ConfigureService(IServiceCollection services)
{
services.AddCors();
...
services.AddOptions();
services.AddHostedService<RepositoryManagerInitializer>();
services.Configure<Myconfig>(Configuration.GetSection("<relevant section key in the app settings>"));
}
The problem was that when reading the section in the configuration file, not like in the development env., need to include the all "path", meaning - if I would like to read the "system" node, and it is inside "AppSettings" node, then I would need to write
Configuration.GetSection("AppSettings:system") and not just Configuration.GetSection("system") as it was (worked in development)

Logging in ASP.NET Core Main

I've an ASP.NET Core 3 WebAPI with a simple Main and a CreateHostBuilder.
public static void Main(String[] args)
{
CreateHostBuilder(args).Build().Run();
}
Later logging in my controllers etc. works fine.
But how can I log possible errors in the Main?
You can get get the logger using
// Build the host and configurations
var host = CreateWebHostBuilder(args).Build();
using (var scope = host.Services.CreateScope())
{
// Get the registered logger service
var logger = scope.ServiceProvider.GetRequiredService<ILogger<YourClassName>>();
logger.LogInformation("Logger test");
}
// finally run the host
host.Run();
Another way.....the advantage to this one is this is a separate logger from the HostBuilder's, so it can log things before the Host is even built. It also can be used throughout the class and outside Main in Program.cs. The disadvantage is it cant use the appsettings.json file to configure it (unless someone can show me how).
public class Program
{
// The Program console apps private logger
private static ILogger _logger;
public static void Main(string[] args)
{
// Now create a logging object for the Program class.
ILoggerFactory loggerFactory = LoggerFactory.Create(builder => builder
.AddConsole()
.AddDebug()
);
_logger = loggerFactory.CreateLogger<Program>();
_logger.LogInformation("PROGRAM CLASS >>> The Program Console Class has started...");
}
}
For Net 6.0, this gist works for me https://gist.github.com/filippovd/edc28b511ef0d7dae9ae8f6eb54c30ed
var builder = WebApplication.CreateBuilder(args);
var logger = LoggerFactory
.Create(loggingBuilder =>
{
// Copy all the current providers that was set within WebApplicationBuilder
foreach (var serviceDescriptor in builder.Logging.Services)
{
loggingBuilder.Services
.Add(serviceDescriptor);
}
})
.CreateLogger<Program>();
;
// Add services to the container.
logger.LogInformation("Add services to the container...");
// Console
// ....
info: Program[0]
Add services to the container...

Add Custom Configuration Source to ASP.NET Core during Startup.Configure

While Microsoft provides an example of adding a custom configuration source in ConfigureAppConfiguration, that is too early for what I need to do, as I need DI to add services before I am ready or even know if I have custom providers to register. Is there anyway I can add to the configuration sources/providers during Startup.Configure? I'm fine this source is only available in subsequent requests after application startup.
In an ASP.NET Core 3.1 project, I've tried injecting IConfigurationRoot but I cannot find a way to add to the Providers enumerable. Any help you can offer would be great.
Here is some pseudo-pseudo code demonstrating what I would like to do in an ideal/fool's world:
public class Startup
{
private IConfigurationRoot ConfigurtionRoot;
public Startup(IWebHostEnvironment env)
{
var builder = new ConfigurationBuilder()
.SetBasePath(env.ContentRootPath)
.AddJsonSettings(env.ContentRootPath, env.EnvironmentName)
.AddEnvironmentVariables();
ConfigurationRoot = builder.Build();
}
public void ConfigureServices(IServicesCollection services)
{
services.AddServicesNeededForCustomConfigProvider();
}
public void Configure(IApplicationBuilder app)
{
var provider = app.ApplicationServices.GetRequiredService<ICustomConfigProvider>();
// This is where we need some magic to add providers/sources after the initial configuration is built.
ConfigurationRoot.AddProvider(provider);
}
}

Nancy 503 Errors switching to OWIN self host

I have some webservices currently hosted with Nancy.Hosting.Self
I need to move the services from Nancy.Hosting.Self to being hosted with Microsoft.Owin.SelfHost so that I can use OWIN for user authentication.
Theoretically, I should be able to simply replace my NancySelfHost class with an Owin Startup class. However, when run the service with my Owin Startup class, Nancy returns: "HTTP Error 503. The service is unavailable."
I am currently swapping the hosting class based on build parameters. (They are launched via TopShelf)
Launcher:
#define OWIN
using Topshelf;
namespace BasisRESTApi
{
public class Program
{
private static readonly string _serviceName = "MyRestApi";
private static readonly string _displayName = "My REST services";
private static readonly string _description = "Minor RESTful web services for interop.";
public static void Main()
{
HostFactory.Run(x =>
{
x.UseLinuxIfAvailable();
// Automate recovery
x.EnableServiceRecovery(recover =>
{
recover.RestartService(0);
});
#if OWIN
x.Service<Startup>(s =>
{
s.ConstructUsing(name => new Startup(_serviceName));
#else
x.Service<NancySelfHost>(s =>
{
s.ConstructUsing(name => new NancySelfHost());
#endif
s.WhenStarted(tc => tc.Start());
s.WhenStopped(tc => tc.Stop());
});
x.StartAutomatically();
x.RunAsLocalSystem();
x.SetDescription(_description);
x.SetDisplayName(_displayName);
x.SetServiceName(_serviceName);
});
}
}
}
NancySelfHost: (Works)
using System;
using System.Configuration;
using System.Net;
using System.Text.RegularExpressions;
using System.Threading;
using Logging;
using Nancy.Hosting.Self;
using static Logging.Logging;
namespace BasisRESTApi
{
public class NancySelfHost
{
private NancyHost _nancyHost;
public void Start()
{
var hostUrl = "https://localhost:2020";
_nancyHost = new NancyHost(new Uri(hostUrl));
_nancyHost.Start();
}
public void Stop()
{
_nancyHost.Stop();
}
}
}
Owin Startup: (Runs but returns 503 Errors)
using Logging;
using Microsoft.Owin;
using Microsoft.Owin.Hosting;
using Owin;
using System;
using System.Configuration;
using System.Net;
using System.Text.RegularExpressions;
using System.Web.Http;
using static Logging.Logging;
[assembly: OwinStartup(typeof(BasisRESTApi.Startup))]
namespace BasisRESTApi
{
public class Startup
{
public string ServiceName { get; set; }
private static IDisposable _application;
public Startup(string serviceName)
{
ServiceName = serviceName;
}
public void Start()
{
var hostUrl = "https://localhost:2020";
_application = WebApp.Start<Startup>(hostUrl);
}
public void Stop()
{
_application?.Dispose();
}
public void Configuration(IAppBuilder application)
{
UseWebApi(application);
application.UseErrorPage();
var listener = (HttpListener)application.Properties["System.Net.HttpListener"];
// Different authentication methods can be specified for the webserver here
listener.AuthenticationSchemes = AuthenticationSchemes.Negotiate;
//NOTE:All of the above can be removed and the issue is not impacted.
application.UseNancy();
}
/// <summary>
/// Provide API Action
/// </summary>
/// <param name="application"></param>
private static void UseWebApi(IAppBuilder application)
{
var config = new HttpConfiguration();
config.MapHttpAttributeRoutes();
application.UseWebApi(config);
}
}
}
Other notes:
UrlAcls and SslCerts are properly set to work for this port, as evidenced by it working with NancySelfHost.
I do not have duplicate urlacl entries as per 503 Error when using NancyFx with Owin
I have tried ports higher than :5000 and it did not help
The same issues occur when run through visual studio as an administrator, or run from the console as an administrator. (Annoyingly, OWIN seems to require admin rights to self-host)
The 503 is generated prior to any of the handler code being run. (IOW, breakpoints at the entry of the webservice code are not hit.)
I discovered the answer here
Essentially, the urlacl that is required for Nancy self-host is not needed for OWIN self hosting, and in fact causes the 503 errors if it is not deleted. (Apparently OWIN uses some other mechanism to get rights to the port -- probably the reason why OWIN requires admin rights to run the .exe or to debug the .exe in Visual Studio)
Running the following resolved the issue:
netsh http delete urlacl url=https://+:2020/

ASP.NET MVC Application with hosted WCF and Windsor: HttpContext.Current is null

I've built an ASP.NET MVC 3 Application hosting a WCF-Service. The service class doing the actual work resides within a class library. I'm trying to use the Windsor WCF facility to wire it up on service request.
Here's my Windsor container factory:
public class WindsorContainerFactory
{
private static IWindsorContainer container;
private static readonly object sync = new Object();
public static IWindsorContainer Current()
{
if(container == null) {
lock (sync)
{
if (container == null)
{
container = new WindsorContainer();
container.Install(new ControllerInstaller());
container.Install(new NHibernateSessionInstaller());
container.Install(new RepositoryInstaller());
container.Install(new ServiceInstaller());
}
}
}
return container;
}
}
In Global.asax, I call WindsorContainerFactory.Current() once to guarantee the factory is beeing built:
protected void Application_Start()
{
WindsorContainerFactory.Current();
ControllerBuilder.Current.SetControllerFactory(typeof(WindsorControllerFactory));
...
I install my service by the following class:
public class ServiceInstaller : IWindsorInstaller
{
public void Install(Castle.Windsor.IWindsorContainer container, Castle.MicroKernel.SubSystems.Configuration.IConfigurationStore store)
{
container.Kernel.AddFacility<WcfFacility>();
container.Kernel.Register(
Component
.For<ICustomerService>()
.ImplementedBy<CustomerService>()
.Named("CustomerService"));
}
}
Then I added a new WCF service to the project, deleted the code behind file and modified the svc markup as follows:
<%# ServiceHost Language="C#" Debug="true"
Factory="Castle.Facilities.WcfIntegration.DefaultServiceHostFactory,
Castle.Facilities.WcfIntegration" Service="CustomerService" %>
As you can see, there are other components beeing installed within windsor (Repositories, Controllers,...), but this seems to work well.
My problem is, that the WCF-client (console app) gets the error:
HttpContext.Current is null. PerWebRequestLifestyle can only be used in ASP.Net
Client code:
CustomerServiceClient c = new Services.Customers.CustomerServiceClient();
CustomerResponse resp = c.GetCustomerBySurname("Lolman");
c.Close();
Can anyone point me to the right direction?
Thank you in advance!
Try enabling AspNetCompatibility.
Check this link for help