How to get Swagger Plugin working within self hosted servicestack - c#

I've re-asked this question with examples provided on github and a drop box download link for anyone that want to run the code themselves : Swagger not working on a self hosted ServiceStack Service
I had my servicestack JSON service running inside my website solution, under the '/api/ path, but now I'd like to split out that service stack portion and have it running as a self hosted windows service. My problem is, myself and the other developers find the Swagger plugin very useful for testing purposes, but now that it's self hosted it appears the HTTPHandler is setup only for only handling the service routes, and plain HTML does not work.
How do I fix this?
URL : http://localhost:8081/Swagger-UI/Index.html
Response :
Handler for Request not found:
Request.HttpMethod: GET
Request.HttpMethod: GET
Request.PathInfo: /Swagger-UI/Index.html
Request.QueryString:
Request.RawUrl: /Swagger-UI/Index.html
Nuget packages installed :
ServiceStack
ServiceStack.Razor
Update for #marfarma
My app.config file has nothing ServiceStack related inside it...
<?xml version="1.0" encoding="utf-8"?>
<configuration>
<startup>
<supportedRuntime version="v4.0" sku=".NETFramework,Version=v4.5" />
</startup>
<system.serviceModel>
<bindings>
<basicHttpBinding>
<binding name="BasicHttpBinding_service1_V2_SSL" />
<binding name="BasicHttpBinding_service2_V1_SSL" />
</basicHttpBinding>
</bindings>
<client>
<!-- bespoke services I'm connecting too -->
</client>
</system.serviceModel>
<appSettings>
<!-- bespoke app settings -->
</appSettings>
</configuration>
Program.cs :
AppHostHttpListenerBase appHost = new my.HttpApi.myApiServiceHostBase("My Service", "my.ApiService");
string listeningURL = "http://localhost:8081/";
var appSettings = new ServiceStack.Configuration.AppSettings();
my.HttpApi.FakeProvider.ProductProvider.Init(appSettings);
my.HttpApi.FakeProvider.UserProvider.Init(appSettings);
#if DEBUG
try
{
appHost.Init();
appHost.Start(listeningURL);
Console.WriteLine("Press <CTRL>+C to stop.");
Thread.Sleep(Timeout.Infinite);
}
catch (Exception ex)
{
Console.WriteLine("ERROR: {0}: {1}", ex.GetType().Name, ex.Message);
throw;
}
finally
{
appHost.Stop();
}
Console.WriteLine("WinServiceAppHost has finished");
configure method :
public override void Configure(Funq.Container container)
{
Plugins.RemoveAll(x => x is CsvFormat);
Plugins.RemoveAll(x => x is HtmlFormat);
ServiceStack.Text.JsConfig.EmitCamelCaseNames = true;
//register any dependencies your services use, e.g:
//container.Register<ICacheClient>(new MemoryCacheClient());
var config = new EndpointHostConfig { DefaultContentType = ContentType.Json, ServiceStackHandlerFactoryPath = "api" };
SetConfig(config);
Plugins.Add(new ServiceStack.Api.Swagger.SwaggerFeature());
Dictionary<Type, string[]> serviceRoutes = new Dictionary<Type, string[]>();
serviceRoutes.Add(typeof(AuthService), new[] { "/auth/user" });
AuthFeature authFeature = new AuthFeature(() => new AuthUserSession(), new IAuthProvider[] { new FakeCredentialsAuthProvider() });
authFeature.IncludeAssignRoleServices = false;
authFeature.ServiceRoutes = serviceRoutes; //specify manual auth routes
Plugins.Add(authFeature);
container.Register<ICacheClient>(new MemoryCacheClient());
var userRep = new InMemoryAuthRepository();
container.Register<IUserAuthRepository>(userRep);
Plugins.Add(new CorsFeature(allowedOrigins: "*",
allowedMethods: "GET, POST, OPTIONS",
//allowedHeaders: "Content-Type",
allowedHeaders : "Origin, X-Atmosphere-tracking-id, X-Atmosphere-Framework, X-Cache-Date, Content-Type, X-Atmosphere-Transport, *",
allowCredentials: false));
}
Update 2 :
1.) Removed the plugins sections above that is removing html and csv
2.) Ran the following in the package manager (nuget) :
Install-Package ServiceStack.Api.Swagger
still no handler found.
The advice in this question ( ServiceStack: No /swagger-ui/index.html ) that marfarma pointed me at suggests I may not have the 'swagger-ui' html and javascript present. I do.
It appears the self hosted servicestack only 'handles' the specified routes, and when I ask the self hosted service to deliver the swagger html and javascript, I get the "handler for request not found" error above.
When I visit the /resources address in my self hosted service, it shows the expected page and data (suggesting the swagger plugin is doing its thing properly), but when I load up the swagger html and javascript from the file system (not 'served' by my service), and provide it the resource url that works, I get "Can't read from server. It may not have the appropriate access-control-origin settings.", which appears to be a CORS issue, but I've enabled cors on my service (it appears an issue with the Swagger UI code), the swagger-ui send my self hosted service a request sent as 'OPTIONS' (instead of GET or POST), and the options request fails :(

as answered in Swagger not working on a self hosted ServiceStack Service:
self hosted services are serving files from bin/debug -> copy always or copy if newer is needed.

Related

How do I specify the "scheme" element using NSwag and C#?

I'm using ASP.NET Core and NSwag to host and describe a new web service hosted in IIS with Windows Authentication.
Locally I run the web service using https, but when I deploy to a test environment the web service sits behind a load balancer with SSL-offloading. This means that even though the site appears to run under SSL in the browser, the actual binding in IIS is http. So my Swagger UI page (and swagger.json definition) describes the schemes supported as http.
I'd like the Schemes element in the Swagger.json that I use to read "https" instead of "http". Would anyone be able to help me find the property I need to set in my code to set the scheme manually?
{
x-generator: "NSwag v11.19.1.0 (NJsonSchema v9.10.72.0 (Newtonsoft.Json v11.0.0.0))",
swagger: "2.0",
info: {
title: "My API title",
description: "Provides access to data.",
version: "1.0.0"
},
host: "myhostname.net",
schemes: [
"http"
],
etc...
}
Boom. Got it!
Finally found an answer on Github and the following code did the trick:
app.UseSwaggerWithApiExplorer(config =>
{
//...other code omitted...
config.PostProcess = settings =>
{
settings.Schemes.Clear();
settings.Schemes.Add(NSwag.SwaggerSchema.Https);
};
});
EDIT:
for NSwag v12 use:
app.UseSwagger(configure => configure.PostProcess = (document, _) => document.Schemes = new[] { SwaggerSchema.Https });
My project was using NSwag v13 and the below worked for me.
app.UseOpenApi(a => {
a.PostProcess = (document, _) => {
document.Schemes = new[] { OpenApiSchema.Https, OpenApiSchema.Http };
};
});
Source:
https://github.com/RicoSuter/NSwag/issues/1545

.NET Core 2.0 MVC odd 404 on controller methods only when deployed to IIS

My home controller sucessfully return index.html from
http://localhost/
and
http://localhost/controller/
but if i try to hit
http://localhost/controller/method/
I get a 404 even though that method works fine in IIS express.
Couldn't find anything online with someone having a similar issue where only the methods on a controller didn't work on one particular deployment but the controller itself is fine.
Things I've Tried that were common among a lot of .Net Core 2.0 issues with IIS Deployments:
Make sure windows authentication is on in project settings and in IIS (I've toggled it on and off to no avail on both I don't have user auth on my web app so I don't think this matters for me).
Switched my application pool to use No managed code for CLR version
Change application pool ID to be LocalSystem
Change permissions on my publish output folder to include %hostname%\IIS_IUSRS
Pretty sure I've also tried a lot of other basic troubleshooting that sometimes fixes issues. I.E. removing and readding app. Turning things on and off again to no avail.
Any suggestions how to troubleshoot this would be very welcome.
I also want to note it was working yesterday and can't remember changing anything other than the publishing output to use Debug instead of Release which of course by now I've changed back to Release but still no luck.
Here is some code
public class MyController : Controller
{
public IActionResult Index()
{
return View();
}
[HttpPost]
public void Store([FromBody]MyObject obj)
{
Console.WriteLine(Request.Body);
//Some code
}
[HttpGet]
public void Check(string objectUID, string idfv)
{
Console.WriteLine($"ObjectUID: {objectUID}");
Console.WriteLine($"IDFV: {idfv}");
//some other code
}
[HttpGet]
public MyObject Retrieve(string objectUID)
{
Console.Writeline($"ObjectUID: {objectUID}");
//Some Code
}
}
This is my routing.
app.UseMvc(routes =>
{
routes.MapRoute(
name: "default",
template: "{controller=MyController}/{action=Index}/{id?}");
});
If you‘re not sure why it doesn’t work, try Attribute-Routing, explained in the Docs
Then you could try it this way:
[Route("[controller]/[action]")]
public class MyController : Controller
{
public IActionResult Index()
{
return View();
}
[HttpPost]
public void Store([FromBody]MyObject obj)
{
Console.WriteLine(Request.Body);
//Some code
}
// If you have a parameter from the uri or query-string, you can add it to the Template this way
[HttpGet("{objectUID} ")]
public void Check(string objectUID, string idfv)
{
Console.WriteLine($"ObjectUID: {objectUID}");
Console.WriteLine($"IDFV: {idfv}");
//some other code
}
// Or optional parameter like this
[HttpGet ("{objectUID?} ")]
public MyObject Retrieve(string objectUID)
{
Console.Writeline($"ObjectUID: {objectUID}");
//Some Code
}
}
Since you are getting 404 error, I suspect the aspnet core handler is missing from your website. Assuming that you have .NET Core Hosting Bundle installed,
ensure you have the following handler added in the web.config file. If web.config file is missing add a new web.config. Also, add a logs folder at the website root folder for logging.
<?xml version="1.0" encoding="utf-8"?>
<configuration>
<!-- To customize the asp.net core module uncomment and edit the following section.
For more info see https://go.microsoft.com/fwlink/?linkid=838655 -->
<system.webServer>
<handlers>
<remove name="aspNetCore"/>
<add name="aspNetCore" path="*" verb="*" modules="AspNetCoreModule" resourceType="Unspecified"/>
</handlers>
<aspNetCore processPath="%LAUNCHER_PATH%" arguments="%LAUNCHER_ARGS%" stdoutLogEnabled="false" stdoutLogFile=".\logs\stdout" />
</system.webServer>
</configuration>
I got ASP.NET Core 2 app running on my machine following these steps:
App Pool setting run under AppPoolIdentity with No Managed runtime configuration
Publish your ASP.NET Core 2 website to a folder. By default it publishes to bin\Release\PublishOutput
Point your IIS website to published folder
I was having a similar issue with getting a 404 when trying to access my site's Account controller. All of the other Razor pages where working correctly. If I change the inheritInChildApplications="false" to true in the web.config the controller starts working.
<?xml version="1.0" encoding="utf-8"?>
<configuration>
<location path="." inheritInChildApplications="true">
...
</configuration>

Handling CORS Preflight in Asp.net Web API

I have three applications in my architecture.
They are on the same server but having different port numbers.
A - Token Application (port 4444) - Asp.net WebApi
B - API Application (port 3333) - Asp.net WebApi
C - UI Application (port 2222) - AngularJS App.
The application flow is like below
1- The UI project gets the token from Token Application (It requires Windows Auth.)
Ex : awxrsdsaWeffs12da
2- UI application puts this token to a custom header which is named as "accessToken"
Ex : accessToken : awxrsdsaWeffs12da
3- UI application sends a request to API Application
Ex: http:myaddress:3333/api/TheRestServiceHere
UI application gets 401 Error.
Which sends OPTIONS method. (I guess preflight issue)
In my web api project I enabled Cors like this below.
public static void Register(HttpConfiguration config)
{
....
//CORS
var cors = new EnableCorsAttribute("*", "*", "*");
config.EnableCors(cors);
....
}
Config
public static class WebApiConfig
{
public static void Register(HttpConfiguration config)
{
//CORS
var cors = new EnableCorsAttribute("*", "*", "*");
config.EnableCors();
// Web API routes
config.MapHttpAttributeRoutes();
config.Routes.MapHttpRoute(
name: "DefaultApi",
routeTemplate: "api/{controller}/{id}",
defaults: new { id = RouteParameter.Optional }
);
var json = config.Formatters.JsonFormatter;
json.SerializerSettings.PreserveReferencesHandling = PreserveReferencesHandling.None;
json.SerializerSettings.ReferenceLoopHandling = ReferenceLoopHandling.Ignore;
json.SerializerSettings.Formatting = Formatting.None;
json.SerializerSettings.ContractResolver = new CamelCasePropertyNamesContractResolver();
config.Formatters.Remove(config.Formatters.XmlFormatter);
}
}
So I am looking for a solution to call API application (B) controllers
and get 200 :)
Regards
I fixed this in an application I am working on by creating a module that responds to requests that are using the OPTIONS verb. You should probably modify it a bit to include the verbs and content type that the application is requesting. In my case, I decided to post everything as JSON (which requires the pre-flight check). The module is as follows:
public class OptionsModule : IHttpModule
{
public void Init(HttpApplication context)
{
context.BeginRequest += (sender, args) =>
{
var app = (HttpApplication) sender;
if (app.Request.HttpMethod == "OPTIONS")
{
app.Response.StatusCode = 200;
app.Response.AddHeader("Access-Control-Allow-Headers", "content-type");
app.Response.AddHeader("Access-Control-Allow-Origin", APISettings.ApplicationOrigin);
app.Response.AddHeader("Access-Control-Allow-Credentials", "true");
app.Response.AddHeader("Access-Control-Allow-Methods", "POST,GET,OPTIONS");
app.Response.AddHeader("Content-Type", "application/json");
app.Response.End();
}
};
}
public void Dispose()
{
}
}
Then you need to register it in your web.config:
<system.webServer>
<modules>
<add name="HandleOptions" type="namespace.OptionsModule" />
</modules>
</system.webServer>
Another thing you may want to do is specify the allowed origin explicitly. Chrome doesn't like having a wildcard there.
One of my friend solved the issue by using OPTIONSVerbHandler.
When UI application wants to use GET method, browser sends
OPTION method first to the server (Preflight). Then if Preflight request is OK it sends GET request.
For CORS test purpose we used the following code to send GET method.
<html>
<head>
<script src="https://code.jquery.com/jquery-1.9.1.min.js"></script>
<script>
$( document ).ready(function() {
var adress = "http://10.10.27.36:3434/backend/api/role";
$.ajaxSetup({
headers: {
'Content-Type': 'application/json',
'accessToken': 'some value',
'Origin' : ''
}
});
$.ajax({
type: "GET",
url: adress,
dataType: "json"
});
});
</script></head><body></body></html>
To handle OPTION method which sends by browser before GET you should have the following settings.
1- Webconfig
<system.webServer>
<handlers>
<add name="OPTIONSVerbHandler" path="*" verb="OPTIONS" modules="ProtocolSupportModule" resourceType="Unspecified" requireAccess="None" />
</handlers>
</system.webServer>
2- Adding OPTIONSVerbHandler with following settings
Click on request restrictions
3- Our Header Settings we have accessToken which is custom as you can see
This problem happened for Cordova v11, platform for Android. I used the solution provided by Jereme (the top rated answer) with one exception: In OptionsModule, I had to omit the statement
app.Response.AddHeader("Access-Control-Allow-Origin", APISettings.ApplicationOrigin);
Instead in the web.config file I added in the <system.webServer> section the following:
<httpProtocol>
<customHeaders>
<add name="Access-Control-Allow-Origin" value="*" />
<!-- Note: "localhost" for Cordova is not valid, only * worked. -->
</customHeaders>
</httpProtocol>
A word about Cordova in case one is unfamiliar. Cordova packages a native app for the Android platform with a “javascript page” included in the app, using a web view in the app to display the page. Using the Chrome debugger to view the javascript page, the page origin appears as localhost. However, localhost is not an acceptable value for the Access-Control-Allow-Origin header; therefore I had to use “*” in the Web.config file. When I had the response header in the OptionsModule, the preflight response was ok (status 200), but not the response for the XMLHttpRequest (the api) that initiated the preflight request. Putting the custom header only in the Web.config file allowed both responses to be ok (status 200).
For Azure Environment
You need allow origins from the portal.

How to consume web service via GET methods?

I have a normal 3rd party SOAP service with WSDL and stuff. The problem is - it only accepts GET requests. How can I access it in c#?
When I add that service to VS via Add Service Reference and try to use it as usual:
var service = new BaseSvcClient(
new BasicHttpContextBinding(),
new EndpointAddress("http://some.internal.ip/WebServices/Base.svc"));
var ver = service.Version();
I see (via fiddler) that it actually sends POST requests and web-service responds with Endpoint not found error message.
If I simply hit http://some.internal.ip/WebServices/Base.svc/Version in a browser the proper xml is returned.
I can use WebClient, but then I have to construct all the GET requests manually, which doesn't look good.
Are there other solutions?
I have found an answer that helped me a lot.
Basically if I take an autogenerated interface for the client, decorate methods with [WebGet] and use
var cf = new WebChannelFactory<IBaseSvc2>(new Uri("..."));
var service = cf.CreateChannel();
var result = service.Version();
it all works well. That's not a perfect solution, since changes won't be picked up automatically, so may be there are other solutions?
P.S. an interface for a web service is now like:
[System.CodeDom.Compiler.GeneratedCodeAttribute("System.ServiceModel", "4.0.0.0")]
[System.ServiceModel.ServiceContractAttribute(ConfigurationName = "BaseService.IBaseSvc")]
public interface IBaseSvc2
{
[System.ServiceModel.OperationContractAttribute(Action = "http://tempuri.org/IBaseSvc/Version", ReplyAction = "http://tempuri.org/IBaseSvc/VersionResponse")]
[WebGet]
VersionInformation Version();
}
You can achieve it by adding the protocols in config file
<webServices>
<protocols>
<add name="HttpGet"/>
<add name="HttpPost"/>
</protocols>
</webServices>

WCF named pipe minimal example

I'm looking for minimal example of WCF Named Pipes (I expect two minimal applications, server and client, which can communicate via a named pipe.)
Microsoft has the briliant article Getting Started Tutorial that describes WCF via HTTP, and I'm looking for something similar about WCF and named pipes.
I've found several posts in the Internet, but they are a little bit "advanced". I need something minimal, only mandatory functionality, so I can add my code and get the application working.
How do I replace that to use a named pipe?
<endpoint address="http://localhost:8000/ServiceModelSamples/Service/CalculatorService"
binding="wsHttpBinding" bindingConfiguration="WSHttpBinding_ICalculator"
contract="ICalculator" name="WSHttpBinding_ICalculator">
<identity>
<userPrincipalName value="OlegPc\Oleg" />
</identity>
</endpoint>
How do I replace that to use a named pipe?
// Step 1 of the address configuration procedure: Create a URI to serve as the base address.
Uri baseAddress = new Uri("http://localhost:8000/ServiceModelSamples/Service");
// Step 2 of the hosting procedure: Create ServiceHost
ServiceHost selfHost = new ServiceHost(typeof(CalculatorService), baseAddress);
try
{
// Step 3 of the hosting procedure: Add a service endpoint.
selfHost.AddServiceEndpoint(
typeof(ICalculator),
new WSHttpBinding(),
"CalculatorService");
// Step 4 of the hosting procedure: Enable metadata exchange.
ServiceMetadataBehavior smb = new ServiceMetadataBehavior();
smb.HttpGetEnabled = true;
selfHost.Description.Behaviors.Add(smb);
// Step 5 of the hosting procedure: Start (and then stop) the service.
selfHost.Open();
Console.WriteLine("The service is ready.");
Console.WriteLine("Press <ENTER> to terminate service.");
Console.WriteLine();
Console.ReadLine();
// Close the ServiceHostBase to shutdown the service.
selfHost.Close();
}
catch (CommunicationException ce)
{
Console.WriteLine("An exception occurred: {0}", ce.Message);
selfHost.Abort();
}
How do I generate a client to use a named pipe?
I just found this excellent little tutorial. broken link (Cached version)
I also followed Microsoft's tutorial which is nice, but I only needed pipes as well.
As you can see, you don't need configuration files and all that messy stuff.
By the way, he uses both HTTP and pipes. Just remove all code lines related to HTTP, and you'll get a pure pipe example.
Try this.
Here is the service part.
[ServiceContract]
public interface IService
{
[OperationContract]
void HelloWorld();
}
public class Service : IService
{
public void HelloWorld()
{
//Hello World
}
}
Here is the Proxy
public class ServiceProxy : ClientBase<IService>
{
public ServiceProxy()
: base(new ServiceEndpoint(ContractDescription.GetContract(typeof(IService)),
new NetNamedPipeBinding(), new EndpointAddress("net.pipe://localhost/MyAppNameThatNobodyElseWillUse/helloservice")))
{
}
public void InvokeHelloWorld()
{
Channel.HelloWorld();
}
}
And here is the service hosting part.
var serviceHost = new ServiceHost
(typeof(Service), new Uri[] { new Uri("net.pipe://localhost/MyAppNameThatNobodyElseWillUse") });
serviceHost.AddServiceEndpoint(typeof(IService), new NetNamedPipeBinding(), "helloservice");
serviceHost.Open();
Console.WriteLine("Service started. Available in following endpoints");
foreach (var serviceEndpoint in serviceHost.Description.Endpoints)
{
Console.WriteLine(serviceEndpoint.ListenUri.AbsoluteUri);
}
Check out my highly simplified Echo example:
It is designed to use basic HTTP communication, but it can easily be modified to use named pipes by editing the app.config files for the client and server. Make the following changes:
Edit the server's app.config file, removing or commenting out the http baseAddress entry and adding a new baseAddress entry for the named pipe (called net.pipe). Also, if you don't intend on using HTTP for a communication protocol, make sure the serviceMetadata and serviceDebug is either commented out or deleted:
<configuration>
<system.serviceModel>
<services>
<service name="com.aschneider.examples.wcf.services.EchoService">
<host>
<baseAddresses>
<add baseAddress="net.pipe://localhost/EchoService"/>
</baseAddresses>
</host>
</service>
</services>
<behaviors>
<serviceBehaviors></serviceBehaviors>
</behaviors>
</system.serviceModel>
</configuration>
Edit the client's app.config file so that the basicHttpBinding is either commented out or deleted and a netNamedPipeBinding entry is added. You will also need to change the endpoint entry to use the pipe:
<configuration>
<system.serviceModel>
<bindings>
<netNamedPipeBinding>
<binding name="NetNamedPipeBinding_IEchoService"/>
</netNamedPipeBinding>
</bindings>
<client>
<endpoint address = "net.pipe://localhost/EchoService"
binding = "netNamedPipeBinding"
bindingConfiguration = "NetNamedPipeBinding_IEchoService"
contract = "EchoServiceReference.IEchoService"
name = "NetNamedPipeBinding_IEchoService"/>
</client>
</system.serviceModel>
</configuration>
The above example will only run with named pipes, but nothing is stopping you from using multiple protocols to run your service. AFAIK, you should be able to have a server run a service using both named pipes and HTTP (as well as other protocols).
Also, the binding in the client's app.config file is highly simplified. There are many different parameters you can adjust, aside from just specifying the baseAddress...
I created this simple example from different search results on the internet.
public static ServiceHost CreateServiceHost(Type serviceInterface, Type implementation)
{
//Create base address
string baseAddress = "net.pipe://localhost/MyService";
ServiceHost serviceHost = new ServiceHost(implementation, new Uri(baseAddress));
//Net named pipe
NetNamedPipeBinding binding = new NetNamedPipeBinding { MaxReceivedMessageSize = 2147483647 };
serviceHost.AddServiceEndpoint(serviceInterface, binding, baseAddress);
//MEX - Meta data exchange
ServiceMetadataBehavior behavior = new ServiceMetadataBehavior();
serviceHost.Description.Behaviors.Add(behavior);
serviceHost.AddServiceEndpoint(typeof(IMetadataExchange), MetadataExchangeBindings.CreateMexNamedPipeBinding(), baseAddress + "/mex/");
return serviceHost;
}
Using the above URI I can add a reference in my client to the web service.

Categories

Resources