Have Swagger to substitute servicestack meta - servicestack

I was wondering if it's possible to have swagger to serve pages at place of SS metadata page... I'm asking this since SS metadata is quite usefull when you've a lot of services
as far I've seen I can remove the feature on SS configuration, disable the httphandler but don't know how to go further
Thanks

So remove the MetaDataFeature in the AppHost Configure method:
SetConfig(new HostConfig {
EnableFeatures = Feature.All.Remove(Feature.Metadata)
});
Then create this simple MetaData service, that redirects to Swagger.
[Route("/metadata/{cmds*}", "GET")]
public class RedirectToSwaggerRequest : IReturnVoid
{
public string cmds { get; set; }
}
[Restrict(VisibleLocalhostOnly = true)]
public class MetadataService : Service
{
public void Get(RedirectToSwaggerRequest request)
{
base.Response.Redirect("/swagger-ui");
}
}
Note: {cmds*} in the route above will catch requests for /metadata, /metadata/something & /metadata/somethingelse etc.
Then when a request goes to /metadata then it will redirect to Swagger instead.

Related

ServiceStack Different Security based on routes

We have a ServiceStack host, in which we have modularised the services. In addition we have a custom authentication solution based on the Basic Authentication. But what we would like to do is have different authentication methods for different services, maybe based on routes? Is this possible?
Secondly, is it possible to assign a common route prefix based on the service? As I said we have modularised our services, and in the AppHost definition we enter the assemblies of the different services, but is it possible to change the route prefix, i.e. Service1 to localhost/api1/servicemethods, Service2 to localhost/api2/servicemethods etc.?
You can limit that a Service should only authenticate with a specific provider by specifying the provider name in the [Authenticate] attribute, e.g:
[Authenticate(AuthenticateService.ApiKeyProvider)]
public class ApiKeyAuthServices : Service
{
public object Any(ApiKeyOnly request) => ...;
}
[Authenticate(AuthenticateService.JwtProvider)]
public class JwtAuthServices : Service
{
public object Any(JwtOnly request) => ...;
}
Otherwise inside your Service you can inspect how the request was authenticated by looking at base.SessionAs<AuthUserSession>().AuthProvider.
For defining dynamic routes have a look at:
Auto Route Generation Strategies
Dynamically adding Route Attributes
Customizing Defined Routes
Although ServiceStack isn't designed to define different sets of Apps within the same AppHost so if that's what you're trying to do I'd recommend instead having different AppHosts and using the Service Gateway for any Service-to-Service communication.
Many thanks for your reply. I must be doing something fundamentally wrong, even though I have registered two custom authproviders, both based on the BasicAuthProvider, using AuthenticateService.GetAuthProviders() returns an empty array.
This is the code I use to register the AuthProviders, and they both allow me to login, so I know they are working.
Plugins.Add(new AuthFeature(() => new CustomUserSession(),
new IAuthProvider[] {
new RMCredentialsAuthProvider(),
new RMKOTAuthProvider()
}));
The code from one of the custom providers is
public class RMKOTAuthProvider : BasicAuthProvider
{
#region Public Constructors
public RMKOTAuthProvider() : base()
{
}
#endregion Public Constructors
#region Public Methods
public override Task<IHttpResult> OnAuthenticatedAsync(IServiceBase authService, IAuthSession session, IAuthTokens tokens, Dictionary<string, string> authInfo, CancellationToken token = default)
{
session.FirstName = session.UserAuthName;
session.Roles = new List<string>
{
"KOT"
};
authService.SaveSessionAsync(session, SessionExpiry);
return base.OnAuthenticatedAsync(authService, session, tokens, authInfo, token);
}
public override Task<bool> TryAuthenticateAsync(IServiceBase authService, string userName, string password, CancellationToken token = default)
{
try
{
if (userName.IsNullOrEmpty() || password.IsNullOrEmpty())
return Task.FromResult(false);
var result = VerifyUser(username, password);
return Task.FromResult(result);
}
catch (InvalidCastException)
{
return Task.FromResult(false);
}
}
#endregion Public Methods
}
Can you please explain what step I am missing such that GetAuthProviders() can list the providers, and I can use the metadata you described earlier.
Many thanks in advance for your help with this.

SwaggerRequestExample attribute does not work in ASP.NET MVC 5 (.NET Framework 4.5.2)

I am toying with Swashbuckle.Examples package (3.10.0) in an ASP.NET MVC project. However, I cannot make request examples appear within the UI.
Configuration (SwaggerConfig.cs)
public static void Register()
{
var thisAssembly = typeof(SwaggerConfig).Assembly;
GlobalConfiguration.Configuration
.EnableSwagger(c => {
c.SingleApiVersion("v1", "TestApp.Web");
c.IncludeXmlComments(string.Format(#"{0}\bin\TestApp.Web.xml", System.AppDomain.CurrentDomain.BaseDirectory));
c.OperationFilter<ExamplesOperationFilter>();
c.OperationFilter<DescriptionOperationFilter>();
c.OperationFilter<AppendAuthorizeToSummaryOperationFilter>();
})
.EnableSwaggerUi(c => { });
}
Request example classes
public class EchoRequestExample : IExamplesProvider
{
public object GetExamples()
{
return new EchoInput { Value = 7 } ;
}
}
public class EchoInput
{
public int Value { get; set; }
}
Action
[HttpGet]
[Route("Echo")]
[CustomApiAuthorize]
[SwaggerRequestExample(typeof(EchoInput), typeof(EchoRequestExample))]
[ResponseType(typeof(EchoServiceModel))]
public HttpResponseMessage Echo([FromUri] EchoInput model)
{
var ret = new EchoServiceModel
{
Username = RequestContext.Principal.Identity.Name,
Value = value
};
return Request.CreateResponse(HttpStatusCode.OK, ret);
}
Swagger UI shows xml comments and output metadata (model and an example containing default values), but shows no request example. I attached to process and EchoRequestExample.GetExamples is not hit.
Question: How to make SwaggerRequestExample attribute work in ASP.NET MVC 5?
Note: Windows identity is used for authorization.
I received an answer from library owner here:
Swagger request examples can only set on [HttpPost] actions
It is not clear if this is a design choice or just a limitation, as I find [HttpGet] examples also relevant.
I know the feeling, lot's of overhead just for an example, I struggle with this for a while, so I created my own fork of swashbuckle, and after unsuccessful attempts to merge my ideas I ended up detaching and renaming my project and pushed to nuget, here it is: Swagger-Net
An example like that will be:
[SwaggerExample("id", "123456")]
public IHttpActionResult GetById(int id)
{
Here the full code for that: Swagger_Test/Controllers/IHttpActionResultController.cs#L26
Wondering how that looks like on the Swagger-UI, here it is:
http://swagger-net-test.azurewebsites.net/swagger/ui/index?filter=IHttpActionResult#/IHttpActionResult/IHttpActionResult_GetById

Access SignalR Hub without Constructor Injection

With AspNetCore.SignalR (1.0.0 preview1-final) and AspNetCore.All (2.0.6), how can I invoke a method on a hub in server code that is not directly in a Controller and is in a class that cannot be made via Dependency Injection?
Most examples assume the server code is in a Controller and should 'ask' for the hub via an injectable parameter in a class that will created by DI.
I want to be able to call the hub's method from server code at any time, in code that is not injected. The old SignalR had a GlobalHost that enabled this approach. Basically, I need the hub to be a global singleton.
Now, everything seems to be dependent on using Dependency Injection, which is introducing a dependency that I don't want!
I've seen this request voiced in a number of places, but haven't found a working solution.
Edit
To be more clear, all I need is to be able to later access the hubs that I've registered in the Configure routine of the Startup class:
app.UseSignalR(routes =>
{
routes.MapHub<PublicHubCore>("/public");
routes.MapHub<AnalyzeHubCore>("/analyze");
routes.MapHub<ImportHubCore>("/import");
routes.MapHub<MainHubCore>("/main");
routes.MapHub<FrontDeskHubCore>("/frontdesk");
routes.MapHub<RollCallHubCore>("/rollcall");
// etc.
// etc.
});
If I register them like this:
services.AddSingleton<IPublicHub, PublicHubCore>();
it doesn't work, since I get back an uninitiated Hub.
No It's not possible. See "official" answer from david fowler https://github.com/aspnet/SignalR/issues/1831#issuecomment-378285819
How to inject your hubContext:
Best solution is to inject your hubcontext like IHubContext<TheHubWhichYouNeedThere> hubcontext
into the constructor.
See for more details:
Call SignalR Core Hub method from Controller
Thanks to those who helped with this. Here's what I've ended up on for now...
In my project, I can call something like this from anywhere:
Startup.GetService<IMyHubHelper>().SendOutAlert(2);
To make this work, I have these extra lines in Startup.cs to give me easy access to the dependency injection service provider (unrelated to SignalR):
public static IServiceProvider ServiceProvider { get; private set; }
public static T GetService<T>() { return ServiceProvider.GetRequiredService<T>(); }
public void Configure(IServiceProvider serviceProvider){
ServiceProvider = serviceProvider;
}
The normal SignalR setup calls for:
public void Configure(IApplicationBuilder app){
// merge with existing Configure routine
app.UseSignalR(routes =>
{
routes.MapHub<MyHub>("/myHub");
});
}
I don't want all my code to have to invoke the raw SignalR methods directly so I make a helper class for each. I register that helper in the DI container:
public void ConfigureServices(IServiceCollection services){
services.AddSingleton<IMyHubHelper, MyHubHelper>();
}
Here's how I made the MyHub set of classes:
using Microsoft.AspNetCore.SignalR;
using System.Threading.Tasks;
public class MyHub : Hub { }
public interface IMyHubHelper
{
void SendOutAlert(int alertNumber);
}
public class MyHubHelper : IMyHubHelper
{
public IHubContext<MyHub> HubContext { get; }
public MyHubHelper(IHubContext<MyHub> hubContext)
{
HubContext = hubContext;
}
public void SendOutAlert(int alertNumber)
{
// do anything you want to do here, this is just an example
var msg = Startup.GetService<IAlertGenerator>(alertNumber)
HubContext.Clients.All.SendAsync("serverAlert", alertNumber, msg);
}
}
This is a nice solution. In .NET Core 2.1 the service provider is disposed and you get cannot access disposed object. The fix is to create a scope:
public void Configure(IApplicationBuilder app, IHostingEnvironment env, IServiceProvider serviceProvider)
{
ServiceProvider = serviceProvider.CreateScope().ServiceProvider;

ServiceStack default Razor view with service

I want to host a very simple razor page inside a self host SS app.
I need the / path to resolve to the default.cshtml - this works out of the box.
But i need to access the user auth session inside the view. To do this I am guessing I need a service to create the model for default.cshtml
Everything I have tried so far doesn't work and I can't create a DefaultRequest with route / as that isn't allowed.
Anyone got any clues as to what I need to do?
I have tried with fall back route but no luck:
[FallbackRoute("/{Path*}")]
public class Fallback
{
public string Path { get; set; }
}
public class DefaultService : Service
{
public DefaultService ()
{
}
public object Get(Fallback request){
return new HttpResult() // #6
{
View = "Rockstars" // #1
};
}
}
Your typed UserAuth session is directly accessible in your Razor Views base ViewPageBase with base.SessionAs, e.g:
#{
var session = base.SessionAs<CustomUserSession>();
}
You've also got access to your dynamic session bag with base.SessionBag as well as base.IsAuthenticated to determine if the user is authenticated or not.
Fallback Route
In order to invoke a Service to handle your default page you need to use a Fallback Route, e.g:
[FallbackRoute("/{Path*}")]
public class DefaultPage
{
public string Path { get; set; }
}
A Fallback Service can be used to handle every unmatched request including the root / page.

Enable gzip/deflate compression

I'm using ServiceStack (version 3.9.44.0) as a Windows Service (so I'm not using IIS) and I use both its abilities both as an API and for serving web pages.
However, I haven't been able to find how exactly I should enable compression when the client supports it.
I imagined that ServiceStack would transparently compress data if the client's request included the Accept-Encoding:gzip,deflate header, but I'm not seeing any corresponding Content-Encoding:gzip in the returned responses.
So I have a couple of related questions:
In the context of using ServiceStack as a standalone service (without IIS), how do I enable compression for the responses when the browser accepts it.
In the context of a C# client, how do similarly I ensure that communication between the client/server is compressed.
If I'm missing something, any help would be welcome.
Thank you.
If you want to enable compression globally across your API, another option is to do this:
Add this override to your AppHost:
public override IServiceRunner<TRequest> CreateServiceRunner<TRequest>(ActionContext actionContext)
{
return new MyServiceRunner<TRequest>(this, actionContext);
}
Then implement that class like this:
public class MyServiceRunner<TRequest> : ServiceRunner<TRequest>
{
public MyServiceRunner(IAppHost appHost, ActionContext actionContext) : base(appHost, actionContext)
{
}
public override void OnBeforeExecute(IRequestContext requestContext, TRequest request)
{
base.OnBeforeExecute(requestContext, request);
}
public override object OnAfterExecute(IRequestContext requestContext, object response)
{
if ((response != null) && !(response is CompressedResult))
response = requestContext.ToOptimizedResult(response);
return base.OnAfterExecute(requestContext, response);
}
public override object HandleException(IRequestContext requestContext, TRequest request, Exception ex)
{
return base.HandleException(requestContext, request, ex);
}
}
OnAfterExecute will be called and give you the chance to change the response. Here, I am compressing anything that is not null and not already compressed (in case I'm using ToOptimizedResultUsingCache somewhere). You can be more selective if you need to but in my case, I'm all POCO objects with json.
References
ServiceStack New Api
For those interested, a partial answer to my own question, you can use the extension method ToOptimizedResult() or, if you are using caching ToOptimizedResultUsingCache().
For instance, returning a compressed result:
public class ArticleService : Service
{
public object Get(Articles request) {
return base.RequestContext.ToOptimizedResult(
new List<Articles> {
new Article {Ref = "SILVER01", Description = "Silver watch"},
new Article {Ref = "GOLD1547", Description = "Gold Bracelet"}
});
}
}
References
CachedServices.cs example
CompressedResult.cs
Google Group question on Compression in ServiceStack

Resources