There is problem with Session in Service, Session is null on second call (solved, see bottom of the post).
I have self-hosted server and client that makes calls to server via JsonServiceClient and ProtoBufServiceClient.
On start of client application I call:
var baseUrl = ConfigGlobal.Host ;
var client = new JsonServiceClient(baseUrl);
var authResponse = client.Post<AuthResponse>("/auth", new Auth
{
UserName = "test1",
Password = "password",
RememberMe = true
});
It works - OnAuthenticated it's fired in my CustomUserSession : AuthUserSession.
authService.SaveSession(session);
didn't help.
Then in one class:
var client = new ProtoBufServiceClient(ConfigGlobal.Host);
client.Put(new ExcelInitialize {Filename = ""}); // OK
Model = client.Get(...); // Session is null
There is a problem in service class in Get method Session is null. If I implement
public CustomUserSession CustomUserSession
{
get
{
return SessionAs<CustomUserSession>();
}
}
I'll get: Only ASP.NET Requests accessible via Singletons are supported.
My AppHost.cs
container.Register<ICacheClient>(new MemoryCacheClient());
container.Register<ISessionFactory>(c => new SessionFactory(c.Resolve<ICacheClient>()));
Plugins.Add(new AuthFeature(
() => new CustomUserSession(), new IAuthProvider[]
{
new CustomCredentialsAuthProvider(),
new BasicAuthProvider(),
}));
Plugins.Add(new RegistrationFeature());
Goal:
Send some variables from client and remember them on host until user logs off.
Edit:
My Workflow looks like this:
SomeClass1:
new auth service client->Post(new Auth(...)); // login ok
nothing more
SomeClass2:
new service client->Put(new E); // some init.
service client->Get(new G);
Service G:
on G request new service client TryResolve();
client->Get(new W)
Service E
on E request CustomUserSession accessible
on W request CustomUserSession not accessible.
My Custom* classes looks like in Scott answer.
Edit:
Here is the code of my problem ready to copy&paste:
private static void Main(string[] args)
{
// Very basic console host
var appHost = new AppHost();
appHost.Init();
appHost.Start("http://*:8082/");
var url = "http://localhost:8082";
var foo = new TestApp.SomeClass1(url);
var bar = new TestApp.SomeClass2(url);
Console.ReadKey();
}
public class AppService : Service
{
public CustomUserSession CustomUserSession
{
get
{
// Returns the typed session
return SessionAs<CustomUserSession>();
}
}
}
public class GService : AppService
{
public object Get(GRequest request)
{
var client = base.TryResolve<EService>();
client.Get(new WRequest());
return new { CustomUserSession.SuperHeroIdentity };
}
}
public class EService : AppService
{
public void Get(WRequest wRequest)
{
Console.WriteLine(CustomUserSession.SuperHeroIdentity);
}
public void Get(ERequest request)
{
Console.WriteLine(CustomUserSession.SuperHeroIdentity);
}
public void Put(ERequest request)
{
Console.WriteLine(CustomUserSession.SuperHeroIdentity);
}
}
public class SomeClass1
{
public SomeClass1(string url)
{
var client = new JsonServiceClient(url);
client.Post<AuthResponse>("/auth", new Auth
{
UserName = "clark.kent",
Password = "kryptonite",
RememberMe = true
});
}
}
public class SomeClass2
{
public SomeClass2(string url)
{
var client = new JsonServiceClient(url);
client.Put(new ERequest());
client.Get(new GRequest());
}
}
public class GRequest : IReturnVoid
{
}
public class ERequest : IReturnVoid
{
}
public class WRequest : IReturnVoid
{
}
Solution (for this problem):
Save session cookies in client application and restore them before every call to Webservice.
Use Service::Resolve() instead of Service::TryResolve
The code you have posted looks okay to me. So it's likely something trivial with your setup.
I have created a simple, self hosted app which also uses a CustomUserSession and a CustomCredentialsAuthProvider, hopefully using this as a guide will highlight what is going wrong. Let me know how you get on.
using ServiceStack.CacheAccess;
using ServiceStack.CacheAccess.Providers;
using ServiceStack.ServiceInterface;
using ServiceStack.ServiceInterface.Auth;
using ServiceStack.ServiceHost;
using ServiceStack.WebHost.Endpoints;
namespace Testv3
{
class MainClass
{
public static void Main()
{
// Very basic console host
var appHost = new AppHost();
appHost.Init();
appHost.Start("http://*:8082/");
Console.ReadKey();
}
}
public class AppHost : AppHostHttpListenerBase
{
public AppHost() : base("Test Service", typeof(TestApp).Assembly) {}
public override void Configure(Funq.Container container)
{
// Cache and session IoC
container.Register<ICacheClient>(new MemoryCacheClient());
container.Register<ISessionFactory>(c => new SessionFactory(c.Resolve<ICacheClient>()));
// Register the Auth Feature with the CustomCredentialsAuthProvider.
Plugins.Add(new AuthFeature(
() => new CustomUserSession(),
new IAuthProvider[]
{
new CustomCredentialsAuthProvider(),
new BasicAuthProvider(),
})
);
}
}
public class CustomCredentialsAuthProvider : CredentialsAuthProvider
{
public override bool TryAuthenticate(IServiceBase authService, string userName, string password)
{
// Replace with a database lookup
return (userName == "clark.kent" && password == "kryptonite");
}
public override void OnAuthenticated(IServiceBase authService, IAuthSession session, IOAuthTokens tokens, Dictionary<string, string> authInfo)
{
var customSession = session as CustomUserSession;
if(customSession != null)
{
// Replace these static values with a database lookup
customSession.FirstName = "Clark";
customSession.LastName = "Kent";
customSession.SuperHeroIdentity = "Superman";
}
authService.SaveSession(customSession, SessionExpiry);
}
}
public class CustomUserSession : AuthUserSession
{
// Our added session property
public string SuperHeroIdentity { get; set; }
}
public static class TestApp
{
[Route("/SuperHeroTime", "GET")]
public class SuperHeroTimeRequest {}
public class TestController : Service
{
public CustomUserSession CustomUserSession
{
get
{
// Returns the typed session
return SessionAs<CustomUserSession>();
}
}
[Authenticate]
public object Get(SuperHeroTimeRequest request)
{
// Return the result object
return new { CustomUserSession.FirstName, CustomUserSession.LastName, Time = DateTime.Now.ToString(), CustomUserSession.SuperHeroIdentity };
}
}
}
}
If you put this index.html in your bin folder and navigate to http://localhost:8082/index.html you can call the service, to test it.
<!doctype html>
<html>
<head>
<title>Test</title>
<script src="//ajax.googleapis.com/ajax/libs/jquery/1.10.2/jquery.min.js"></script>
<script>
function login()
{
$.ajax({
type: "POST",
url: "/auth/credentials",
contentType: "application/json",
data: JSON.stringify({
UserName: "clark.kent",
Password: "kryptonite",
RememberMe: true
})
}).done(function(result){
getSuperHeroTime();
});
}
function getSuperHeroTime()
{
$.ajax({
type: "GET",
url: "/SuperHeroTime",
contentType: "application/json",
}).done(function(result){
$("#result").html(result.FirstName + " " +
result.LastName + " is " +
result.SuperHeroIdentity + " (" +
result.Time + ")");
});
}
</script>
</head>
<body>
<h1>Super Hero Time</h1>
<button onclick="login()">Go</button>
<div id="result"></div>
</body>
</html>
Update:
Having looked at your usage code provided in your edit. You are calling the Auth method in SomeClass1 and then that JsonServiceClient isn't reused. So your session won't follow. You have to reuse the client, because the client stores the cookies that track your session.
In SomeClass2 you are effectively calling the service without authenticating, so the session is null. You need to reuse the same JsonServiceClient for your requests.
You could run your authentication method and then pass the session cookies to any subsequent JsonServiceClient, to save re-authenticating with each client. It's easy to do:
var authClient = new JsonServiceClient(url);
authclient.Post<AuthResponse>("/auth", new Auth {
UserName = "clark.kent",
Password = "kryptonite",
RememberMe = true
});
// Get the session cookies
var cookies = authClient.CookieContainer.GetCookies(new Uri(url));
// In your other `JsonServiceClient`s set the cookies before making requests
var anotherClient = new JsonServiceClient(url);
anotherClient.CookiesCollection.add(cookies);
anotherClient.Post( ...
You are also trying to resolve another service within your service using:
base.TryResolve<EService>();
You need to use this instead, or you will see the Only ASP.NET Requests accessible via Singletons are supported exception:
base.ResolveService<EService>();
Related
I have a username and password in an existing web service, and I want to use the username and password that I have in a method called Login().
I have the followings records:
APIApp.cs
namespace App.Work.API
{
public class APIApp : IAPIApp
{
private readonly LogInServiceClient _loginService;
public APIApp()
{
String url = #"http://(An IP)";
BasicHttpBinding binding = new BasicHttpBinding();
this._loginService = new LogInServiceClient(binding, new EndpointAddress(url + "/Services/LogInService.svc"));
}
// Invocando Login
public void Login()
{
using (OperationContextScope scope = new OperationContextScope(_loginService.InnerChannel))
{
// Pasar los parametros por cabecera.
// Setting the headers
var request = new HttpRequestMessageProperty();
request .Headers["Language"] = Language;
request .Headers["ModuleId"] = ModuleId.ToString(CultureInfo.CurrentCulture);
OperationContext.Current.OutgoingMessageProperties[HttpRequestMessageProperty.Name] = request ;
LogInInfoDTO loginInfo = _loginService.AreUserAndPasswordCorrect("(An username)", "(A password)", Extension);
if (loginInfo.Authenticated)
{
(I don't know if I must to put anything there.)
}
}
}
}
}
IAPIApp.cs
namespace APP.Work.API
{
public interface IAPIResuPlus
{
void Login();
void ObtenerTrabajadores();
}
}
A controller:
using APP.Work.Services.LogInService;
using Microsoft.AspNetCore.Mvc;
namespace APP.Work.View;
[Route("api/[controller]")]
[ApiController]
public class APIAPPController : ControllerBase
{
IAPIApp _api;
public APIAPPController(IAPIApp api)
{
this._api = api;
}
[HttpGet("[action]")]
public IActionResult Login()
{
_api.Login();
return Ok();
}
}
The view:
I'm sorry if I didn't put the code correctly, but since I'm new to using apis, I'm also new to this forum xD thanks for your help!
I am trying to send the Session Id of an ASP.NET Core 5 application using Razor Pages to my SignalR Core Hub. However, the Session Id is only added to the negotiate request, not to the actual WebSocket that is being opened:
How can I add it to the sessionsHub request as well, which is used by the OnConnected() method in the hub?
The Razor Page .cs:
public class IndexModel : PageModel
{
private readonly HttpContext _httpContext;
public string SessionId { get; set; }
public IndexModel(IHttpContextAccessor httpContextAccessor)
{
_httpContext = httpContextAccessor.HttpContext;
}
public async Task OnGet()
{
SessionId = _httpContext.Session.Id;
}
}
The .cshtml using a querystring, I've also tried adding a Session-Id as Header to the request, same result:
#page
#model IndexModel
<script type="text/javascript">
var sessionId = "#Model.SessionId";
class CustomHttpClient extends signalR.DefaultHttpClient {
send(request) {
var url = new URL(request.url);
if(!url.search){
url.href = url.href + '?sessionId="' + sessionId + '"';
}else{
url.href = url.href + '&sessionId="' + sessionId + '"';
}
request.url = url.href;
return super.send(request);
}
}
var connection = new signalR.HubConnectionBuilder().withUrl("/sessionsHub", { httpClient: new CustomHttpClient() }).build();
connection.start().then(() => {
}).catch(function (err) {
return console.error(err.toString());
});
</script>
The hub:
public class SessionHub : Hub{
public override async Task OnConnectedAsync()
{
await base.OnConnectedAsync();
string sessionId = GetSessionId();
}
private string GetSessionId()
{
HttpContext httpContext = Context.GetHttpContext();
List<StringValues> sessionIdQueryString = httpContext.Request.Query.Where(x => x.Key == "sessionId").Select(x => x.Value).ToList();
if (sessionIdQueryString.Count == 0)
{
throw new NullReferenceException();
}
string sessionId = sessionIdQueryString.First();
return sessionId;
}
}
You need to enter the sessionid value inside the url when creating the connetion.
so that it can be accessed in all hub methods The code will look like this :
var connection = new signalR.HubConnectionBuilder()
.withUrl("/sessionsHub/?sessionId=#Model.SessionId")
.build();
connection.start().then(function(){
//ToDo
}).catch(function (err) {
console.log(err);
});
There is a need to receive user data using a token. Hello. There is a need to receive user data using a token. I have a web api + websockets, websockets connection via a web browser.
var webSocket = new WebSocket(handlerUrl);
//Open connection handler.
webSocket.onopen = function () {
webSocket.send("{\"type\":\"LOGIN\",\"access_token\":\"Bearer HIDDEN\"}");
};
Once connected, I immediately send token.
On the server side, it looks as follows:
public class SocketClientController: ApiController
{
public HttpResponseMessage Get ()
{
HttpContext.Current.AcceptWebSocketRequest (new WebSocketHandler ());
return Request.CreateResponse (HttpStatusCode.SwitchingProtocols);
}
<miss>
Socket class:
<miss>
private void Login(string access_token)
{
// here i want get user info
}
public override void OnMessage(string input)
{
dynamic data = JObject.Parse(input);
switch ((string)data.type)
{
case "LOGIN":
Login((string)data.access_token);
break;
}
}
I use Identity, a variant with a token when you first received from the client suits me the data. Tell me how you can get the user input without going through the login and password, and use [Authorize].
Sorry for my english.
I decided my task! Below is the code:
public class MachineKeyProtector : Microsoft.Owin.Security.DataProtection.IDataProtector
{
private readonly string[] _purpose =
{
typeof(OAuthAuthorizationServerMiddleware).Namespace,
"Access_Token",
"v1"
};
public byte[] Protect(byte[] userData)
{
throw new NotImplementedException();
}
public byte[] Unprotect(byte[] protectedData)
{
return System.Web.Security.MachineKey.Unprotect(protectedData, _purpose);
}
}
Use:
var secureDataFormat = new TicketDataFormat(new Providers.MachineKeyProtector());
AuthenticationTicket ticket = secureDataFormat.Unprotect(access_token);
var userId = ticket.Identity.GetUserId();
My connection does not start.
This code worked in 1.x but in version 2 is not working.
SignalR seems to be trying to connect but without success.
The hub method is never called.
Attached sending an image with SignalR debug.
Javascript:
<script type="text/javascript">
$.connection.hub.logging = true;
var options = { transport: ['webSockets', 'longPolling'] };
$(function() {
var userHub = $.connection.userHub;
//Iniciar connecção
window.hubReady = $.connection.hub.start(options);
window.hubReady.done(function () {
userHub.server.ini();
});
userHub.client.iniDone = function (connectionId) {
console.log(connectionId);
};
$.connection.hub.connectionSlow(function() {
console.log('slow connection...');
});
window.hubReady.fail(function(error) {
console.log(error);
});
$.connection.hub.disconnected(function() {
setTimeout(function() {
$.connection.hub.start();
}, 2000);
});
});
</script>
Hub:
[HubName("userHub")]
public class UserHub : Hub
{
public void Ini()
{
Clients.Client(Context.ConnectionId).iniDone(string.Format("Conectado com o id: {0}", Context.ConnectionId));
}
public override Task OnConnected()
{
var connectionId = Context.ConnectionId;
var email = string.IsNullOrWhiteSpace(Context.User.Identity.Name) ? Context.Headers["email"] : Context.User.Identity.Name;
if (email != null && connectionId != null)
UserData.GetInstance(email).ConnectionsIds.Add(connectionId);
return base.OnConnected();
}
public override Task OnDisconnected()
{
var connectionId = Context.ConnectionId;
var email = string.IsNullOrWhiteSpace(Context.User.Identity.Name) ? Context.Headers["email"] : Context.User.Identity.Name;
if (email != null && connectionId != null)
UserData.GetInstance(email).ConnectionsIds.Remove(connectionId);
return base.OnDisconnected();
}
}
Debug:
SignalR Debug Image
EDIT:
I found the problem! The GetInstance method of my Singleton has problems.
public static UserData GetInstance(string username)
{
if (_sharedUsers == null)
lock (_lockCreate)
_sharedUsers = new Dictionary<string, UserData>();
if (!_sharedUsers.ContainsKey(username))
lock (_lockAdd)
_sharedUsers.Add(username, new UserData(username));
return _sharedUsers[username];
}
the method stops always here: lock (_lockAdd)
I want to save all user connectionsIds Any ideas?
Thanks
Try moving the client method subscription to be before you connect. If it's not registered by the time the connection is started, then it will not be callable from the server.
So change it to the following:
$(function() {
var userHub = $.connection.userHub;
//Register Client handlers first
userHub.client.iniDone = function (connectionId) {
console.log(connectionId);
};
//Now you can connect.
window.hubReady = $.connection.hub.start(options);
window.hubReady.done(function () {
userHub.server.ini();
});
$.connection.hub.connectionSlow(function() {
console.log('slow connection...');
});
window.hubReady.fail(function(error) {
console.log(error);
});
$.connection.hub.disconnected(function() {
setTimeout(function() {
$.connection.hub.start();
}, 2000);
});
});
Edit
Based on your comment around a server error in the OnConnected method, it seems like you may have a two problems then. Isolate the connection tracking part out (just comment it out) to get the full round-trip going between client and server. Then add back the connection tracking which is possibly a DB connection error - check the server logs.
Edit
In terms of storing the user connections, you've a few options.
Use ConcurrentDictionary:
One of the simplest is storing in a static ConcurrentDictionary, similar to what you have. Try to avoid the use of so many locks - using a ConcurrentDictionary means you'll actually end up with none.
e.g.
public class UserData
{
public UserData(string username)
{
UserName = username;
ConnectionIds = new HashSet<string>();
}
public string UserName { get; private set; }
public HashSet<string> ConnectionIds { get; private set; }
}
public static class ConnectionStore
{
private static readonly ConcurrentDictionary<string, UserData> _userData = new ConcurrentDictionary<string, UserData>();
public static void Join(string username, string connectionId)
{
_userData.AddOrUpdate(username,
u => new UserData(u), /* Lambda to call when it's an Add */
(u, ud) => { /* Lambda to call when it's an Update */
ud.ConnectionIds.Add(connectionId);
return ud;
});
}
}
See MSDN for more info: http://msdn.microsoft.com/en-us/library/ee378675(v=vs.110).aspx
Use a database:
The other option is to store in a database (using Entity Framework) which has the added benefit of tracking user data across server recycles.
Have a look at http://www.asp.net/signalr/overview/signalr-20/hubs-api/mapping-users-to-connections which shows all these options a couple of others.
Had the same problem for so long, so gave up the whole signalR at some point, but had to pick it up again for our project:
I have written an answer which might lead you and others on the right track (step by step)...In the answer I am using PersistentConnection rather than Hub, but the principle should be the same:
https://stackoverflow.com/a/25304790/3940626
I'm working through some Functional Tests on my app, and I think I'm getting pretty close. My problem is that when I run my first test, I get the error.
unable to connect to the remote server.
Expected: OK
But was: 0
I can confirm that if I put a breakpoint on the Assert, and then try to hit the BaseUrl in my browser, it is not found.
Here's my test.
[Test]
public void MyTestTest ()
{
var client = new RestClient( ServiceTestAppHostBase.BaseUrl );
// client.Authenticator = new HttpBasicAuthenticator( NUnitTestLoginName, NUnitTestLoginPassword );
var request = new RestRequest( "/users/", Method.GET );
request.RequestFormat = DataFormat.Json;
var response = client.Execute( request );
// do assertions on the response object now
Assert.That( response.StatusCode, Is.EqualTo( HttpStatusCode.OK ) );
}
The AppServerTestSetup looks like this
[SetUpFixture]
public class AppServerTestSetup
{
ServiceTestAppHostBase _appHost;
[SetUp]
public void SetUp()
{
_appHost = new ServiceTestAppHostBase();
_appHost.Init();
_appHost.Start(ServiceTestAppHostBase.BaseUrl);
}
[TearDown]
public void TearDown()
{
_appHost.Dispose();
}
}
And the ServiceTestAppHostBase looks like this.
public class ServiceTestAppHostBase : AppHostHttpListenerBase
{
public const string BaseUrl = "http://localhost:8082/";
public ServiceTestAppHostBase () : base( "OurApp.AppServer", typeof( UserServiceInterface ).Assembly ) { }
public override void Configure ( Container container )
{
JsConfig.EmitCamelCaseNames = true;
SetConfig( new EndpointHostConfig
{
MapExceptionToStatusCode = {
{ typeof( NotFoundException ), 404 }, // Map exception to 404 not found http response.
{ typeof( SystemAccountChangeException ), 405 } // Map exception to 405 method not allowed.
}
} );
// Shared Container Registration
AppHostContainerRegistrations.Register( container );
// Setup the database
var migrationRunner = container.Resolve<IMigrationRunner>();
migrationRunner.CreateDatabase();
migrationRunner.RunAll();
}
}
note: I'm also using the AppHostContainerRegistrations in the main app, and it is working. I've also verified that it's being run in the test setup.
The AppHostContainerRegistrations (for reference) looks like this.
public class AppHostContainerRegistrations
{
public static void Register(Container container)
{
// IOC Registration
// Register base connection config
var dbConnection = ConfigurationManager.ConnectionStrings["databaseConnection"];
var databaseName = ConfigurationManager.AppSettings["databaseName"];
// Register Sqlserver DbProvider
container.Register<IDbConnectionProvider>( containr => new DbConnectionProvider( dbConnection.ConnectionString, dbConnection.ProviderName ) );
container.Register<IDbProvider>( containr => new DbProvider( containr.Resolve<IDbConnectionProvider>(), databaseName ) );
// Register repositories
container.RegisterAs<DatabaseVersionRepository, IDatabaseVersionRepository>();
container.RegisterAs<UserRepository, IUserRepository>();
container.RegisterAs<GroupRepository, IGroupRepository>();
container.RegisterAs<DeviceRepository, IDeviceRepository>();
container.RegisterAs<SecuritySettingsRepository, ISecuritySettingsRepository>();
// Register services
container.RegisterAs<UserService, IUserService>();
container.RegisterAs<GroupService, IGroupService>();
container.RegisterAs<SecuritySettingsService, ISecuritySettingsService>();
// Register everything else
container.RegisterAs<PasswordHasher, IPasswordHasher>();
container.RegisterAs<MigrationRunner, IMigrationRunner>();
container.Register( new UserModel { Id = new Guid( "6C83DDEC-5E58-4F28-BDE2-61EBF1B49691" ) } );
}
}
The reason we're doing our Db setup like this is because we have a single connection string and db name in the App.Config, and we rely on Transforms during deployment to setup the database.
Can anyone help me troubleshoot this issue?
After a lengthy conversation with #mythz, it turns out that VS has to be run in Admin mode for the "AppHostHttpListenerBase" to run.
I also have to run Powershell as Admin when running ./build from the terminal.