Welcome everyone
First:
I used this sample: "https://github.com/stevenchang0529/XamarinGoogleDriveRest"
It is a project to explain how to use Google Drive API with XamrinForms with creating a text file, saving it and reading its data, by the way, thanks to Steven Chang.
It works fine, but it asks to log in to the account every time I want to upload the file to Google Drive, how can I make it save the login data after entering it for the first time.
Second:
How can I make the user choose his account from the popup "like the one in the picture below" instead of using the Web Authenticator Native Browser:
the ViewModel class that used in the project:
public class MainViewModel
{
private string scope = "https://www.googleapis.com/auth/drive.file";
private string clientId = "clientId here";
private string redirectUrl = "url:/oauth2redirect";
public ICommand OnGoogleDrive { get; set; }
public MainViewModel()
{
var auth = new OAuth2Authenticator(
this.clientId,
string.Empty,
scope,
new Uri("https://accounts.google.com/o/oauth2/v2/auth"),
new Uri(redirectUrl),
new Uri("https://www.googleapis.com/oauth2/v4/token"),
isUsingNativeUI: true);
AuthenticatorHelper.OAuth2Authenticator = auth;
auth.Completed +=async (sender, e) =>
{
if (e.IsAuthenticated)
{
var initializer = new GoogleAuthorizationCodeFlow.Initializer
{
ClientSecrets = new Google.Apis.Auth.OAuth2.ClientSecrets()
{
ClientId = clientId
}
};
initializer.Scopes = new[] { scope };
initializer.DataStore = new FileDataStore("Google.Apis.Auth");
var flow = new GoogleAuthorizationCodeFlow(initializer);
var user = "DriveTest";
var token = new TokenResponse()
{
AccessToken=e.Account.Properties["access_token"],
ExpiresInSeconds=Convert.ToInt64( e.Account.Properties["expires_in"]),
RefreshToken=e.Account.Properties["refresh_token"],
Scope=e.Account.Properties["scope"],
TokenType=e.Account.Properties["token_type"]
};
UserCredential userCredential = new UserCredential(flow, user, token);
var driveService = new DriveService(new BaseClientService.Initializer()
{
HttpClientInitializer = userCredential,
ApplicationName = "Quickstart",
});
//test google drive
DriveServiceHelper helper = new DriveServiceHelper(driveService);
var id = await helper.CreateFile();
await helper.SaveFile(id, "test", "test save content");
var content = await helper.ReadFile(id);
}
};
auth.Error += (sender, e) =>
{
};
this.OnGoogleDrive = new Command(() =>
{
var presenter = new OAuthLoginPresenter();
presenter.Login(auth);
});
}
}
public static class AuthenticatorHelper
{
public static OAuth2Authenticator OAuth2Authenticator { get; set; }
}
I am sorry if there are spelling mistakes, because I'm not good at English.
Even with Xamarin Forms, It won't be 100% like your image on iOS device. I think this is a way to make it similar both iOS and Android platform.
Here is some sample code for that.
Shared
mainpage.xaml
This button is only visible when the user is not logged in.
...
<Button Text="Google Login"
Command="{Binding GoogleLoginCommand}"
IsVisible="{Binding IsLogedIn, Converter={StaticResource InvertBooleanConverter}}"/>
...
InvertBooleanConverter.cs
This converter returns false when IsLogedIn is True and true when IsLogedIn is false.
public class InvertBooleanConverter : IValueConverter {
public object Convert(object value, Type targetType, object parameter, CultureInfo culture)
{
return !(bool)value;
}
public object ConvertBack(object value, Type targetType, object parameter, CultureInfo culture)
{
return !(bool)value;
}
}
Model
GoogleUser.cs
public class GoogleUser
{
public string Name { get; set; }
public string Email { get; set; }
public Uri Picture { get; set; }
}
ViewModel
...
public class MainPageViewModel {
private readonly IGoogleManager _googleManager;
public DelegateCommand GoogleLoginCommand { get; set; }
private bool _isLogedIn;
public bool IsLogedIn
{
get { return _isLogedIn; }
set { _isLogedIn = value; }
}
private GoogleUser _googleUser;
public GoogleUser GoogleUser
{
get { return _googleUser; }
set { _googleUser = value; }
}
...
public MainPageViewModel(IGoogleManager googleManager){ //Without IoC
_googleManager = googleManager;
IsLogedIn = false;
GoogleLoginCommand = new DelegateCommand(GoogleLogin);
}
private void GoogleLogin()
{
_googleManager.Login(OnLoginComplete);
}
private void OnLoginComplete(GoogleUser googleUser, string message)
{
if (googleUser != null){
GoogleUser = googleUser;
IsLogedIn = true;
//Your code after login
} else {
//Error
}
}
...
Interface for each device
public interface IGoogleManager
{
void Login(Action<GoogleUser, string> OnLoginComplete);
void Logout();
}
iOS
YOUR_PROJECT_NAME.ios
Download the GoogleService-info.plist file and add it your Xamarin.iOS folder.
Open the GoogleService-info.plist and copy the REVERSED_CLIENT_ID value
Open the info.plist file, go to the Advanced tab, create a new URL with a editor role and paste it in the URL Scheme field.
AppDelegate.cs
add this code to the AppDelegate class
...
var googleServiceDictionary = NSDictionary.FromFile("GoogleService-Info.plist");
SignIn.SharedInstance.ClientID = googleServiceDictionary["CLIENT_ID"].ToString();
...
GoogleManager.cs
public class GoogleManager : NSObject, IGoogleManager, ISignInDelegate, ISignInUIDelegate
{
private Action<GoogleNativeLogin.Models.GoogleUser, string> _onLoginComplete;
private UIViewController _viewController { get; set; }
public GoogleManager()
{
SignIn.SharedInstance.UIDelegate = this;
SignIn.SharedInstance.Delegate = this;
}
public void Login(Action<GoogleNativeLogin.Models.GoogleUser, string> OnLoginComplete)
{
_onLoginComplete = OnLoginComplete;
var window = UIApplication.SharedApplication.KeyWindow;
var vc = window.RootViewController;
while (vc.PresentedViewController != null)
{
vc = vc.PresentedViewController;
}
_viewController = vc;
SignIn.SharedInstance.SignInUser();
}
public void Logout()
{
SignIn.SharedInstance.SignOutUser();
}
public void DidSignIn(SignIn signIn, Google.SignIn.GoogleUser user, NSError error)
{
if (user != null && error == null)
_onLoginComplete?.Invoke(new GoogleNativeLogin.Models.GoogleUser()
{
Name = user.Profile.Name,
Email = user.Profile.Email,
Picture = user.Profile.HasImage ? new Uri(user.Profile.GetImageUrl(500).ToString()) : new Uri(string.Empty)
}, string.Empty);
else
_onLoginComplete?.Invoke(null, error.LocalizedDescription);
}
[Export("signIn:didDisconnectWithUser:withError:")]
public void DidDisconnect(SignIn signIn, GoogleUser user, NSError error)
{
// When the user disconnects from app here
}
[Export("signInWillDispatch:error:")]
public void WillDispatch(SignIn signIn, NSError error)
{
//myActivityIndicator.StopAnimating();
}
[Export("signIn:presentViewController:")]
public void PresentViewController(SignIn signIn, UIViewController viewController)
{
_viewController?.PresentViewController(viewController, true, null);
}
[Export("signIn:dismissViewController:")]
public void DismissViewController(SignIn signIn, UIViewController viewController)
{
_viewController?.DismissViewController(true, null);
}
}
Android
MainActivity.cs
add this code to the MainActivity
...
protected override void OnActivityResult(int requestCode, Result resultCode, Android.Content.Intent data)
{
base.OnActivityResult(requestCode, resultCode, data);
if (requestCode == 1)
{
GoogleSignInResult result = Auth.GoogleSignInApi.GetSignInResultFromIntent(data);
GoogleManager.Instance.OnAuthCompleted(result);
}
}
...
GoogleManager
public class GoogleManager : Java.Lang.Object, IGoogleManager, GoogleApiClient.IConnectionCallbacks, GoogleApiClient.IOnConnectionFailedListener
{
public Action<GoogleUser, string> _onLoginComplete;
public static GoogleApiClient _googleApiClient { get; set; }
public static GoogleManager Instance { get; private set; }
public GoogleManager()
{
Instance = this;
GoogleSignInOptions gso = new GoogleSignInOptions.Builder(GoogleSignInOptions.DefaultSignIn)
.RequestEmail()
.Build();
_googleApiClient = new
GoogleApiClient.Builder(((MainActivity)Forms.Context).ApplicationContext)
.AddConnectionCallbacks(this)
.AddOnConnectionFailedListener(this)
.AddApi(Auth.GOOGLE_SIGN_IN_API, gso)
.AddScope(new Scope(Scopes.Profile))
.Build();
}
public void Login(Action<GoogleUser, string> onLoginComplete)
{
_onLoginComplete = onLoginComplete;
Intent signInIntent = Auth.GoogleSignInApi.GetSignInIntent(_googleApiClient);
((MainActivity)Forms.Context).StartActivityForResult(signInIntent, 1);
_googleApiClient.Connect();
}
public void Logout()
{
_googleApiClient.Disconnect();
}
public void OnAuthCompleted(GoogleSignInResult result)
{
if (result.IsSuccess)
{
GoogleSignInAccount accountt = result.SignInAccount;
_onLoginComplete?.Invoke(new GoogleUser(){
Name = accountt.DisplayName,
Email = accountt.Email,
Picture = new Uri((accountt.PhotoUrl != null ? $ {accountt.PhotoUrl}" : $"//YOUR_PLACE_HOLDER_IMAGE.jpg//"))}, string.Empty);
} else {
_onLoginComplete?.Invoke(null, "An error occured!");
}
}
public void OnConnected(Bundle connectionHint)
{
}
public void OnConnectionSuspended(int cause)
{
_onLoginComplete?.Invoke(null, "Canceled!");
}
public void OnConnectionFailed(ConnectionResult result)
{
_onLoginComplete?.Invoke(null, result.ErrorMessage);
}
}
Related
There is a parent window, a page is declared in it, there is a (Edit) button on the page. By clicking on the button, a window will open, after it is closed, the data in the parent window should be updated.
ViewModel Parent Window
public class MasterWinViewModel : Notify
{
private object _curentPage;
public object CurentPage
{
get { return _curentPage; }
set
{
_curentPage = value;
SignalChanged("CurentPage");
}
}
public UserApi ProfileUser
{
get => profileUser;
set
{
profileUser = value;
SignalChanged("ProfileUser");
}
}
public CustomCommand Main { get; set; }
public CustomCommand Help { get; set; }
public CustomCommand Search { get; set; }
public CustomCommand MyMediaLibrary { get; set; }
public CustomCommand Profile { get; set; }
public CustomCommand Test { get; set; }
private UserApi profileUser;
public MasterWinViewModel()
{
Task.Run(GetUserId);
Test = new CustomCommand(() =>
{
Test nn = new Test();
nn.Show();
});
Main = new CustomCommand(() =>
{
CurentPage = new MainPage();
SignalChanged("CurentPage");
});
Help = new CustomCommand(() =>
{
CurentPage = new HelpPage();
SignalChanged("CurentPage");
});
Search = new CustomCommand(() =>
{
CurentPage = new SearchPage();
SignalChanged("CurentPage");
});
MyMediaLibrary = new CustomCommand(() =>
{
CurentPage = new MyMediaLibraryPage();
SignalChanged("CurentPage");
});
Profile = new CustomCommand(() =>
{
CurentPage = new ProfilePage();
SignalChanged("CurentPage");
});
}
public async Task GetUserId()
{
Task.Delay(200).Wait();
var t = SingInWindowViewModel.UsId;
var result = await Api.GetAsync<UserApi>(t, "User");
ProfileUser = result;
SignalChanged("ProfileUser");
}
}
ViewModel pages
public class ProfilePageViewModel : Notify
{
private CustomCommand selectedAlbum;
private List<AlbumApi> albums;
public CustomCommand SelectedAlbum
{
get => selectedAlbum;
set
{
selectedAlbum = value;
SignalChanged();
}
}
public List<AlbumApi> Albums
{
get => albums;
set
{
albums = value;
SignalChanged("Albums");
}
}
public CustomCommand NewAlbum { get; set; }
public UserApi ProfileUser { get; set; }
public CustomCommand Edit { get; set; }
public CustomCommand EditAlbum { get; set; }
public CustomCommand DeleteAlbum { get; set; }
public CustomCommand Refresh { get; set; }
public ProfilePageViewModel()
{
Task.Run(GetUserId);
Task.Run(GetAlbumsList);
Refresh = new CustomCommand(() =>
{
Task.Run(GetUserId);
Task.Run(GetAlbumsList);
});
Edit = new CustomCommand(() =>
{
EditUserWindow euw = new EditUserWindow();
euw.ShowDialog();
Thread.Sleep(1000);
Task.Run(GetUserId);
});
NewAlbum = new CustomCommand(() =>
{
AddAlbumWindow albumWindow = new AddAlbumWindow();
albumWindow.Show();
});
}
public async Task GetUserId()
{
var t = SingInWindowViewModel.UsId;
var result = await Api.GetAsync<UserApi>(t, "User");
ProfileUser = result;
SignalChanged("ProfileUser");
}
public async Task GetAlbumsList()
{
var result = await Api.GetListAsync<AlbumApi[]>("Album");
Albums = new List<AlbumApi>(result);
SignalChanged("Albums");
}
public void Load()
{
Task.Run(GetUserId);
Task.Run(GetAlbumsList);
}
}
ViewModel editing windows
public class EditUserWindowViewModel : Notify
{
private BitmapImage image;
public BitmapImage Image
{
get => image;
set
{
image = value;
SignalChanged("Image");
}
}
public UserApi EditUser { get; set; }
public CustomCommand SelectImage { get; set; }
public CustomCommand SaveUser { get; set; }
public EditUserWindowViewModel()
{
Task.Run(GetUserId);
SaveUser = new CustomCommand(() =>
{
Task.Run(EditUsers);
foreach (Window window in Application.Current.Windows)
{
if (window.DataContext == this)
{
CloseWin(window);
}
}
});
string directory = Environment.CurrentDirectory;
SelectImage = new CustomCommand(() =>
{
OpenFileDialog ofd = new OpenFileDialog();
if (ofd.ShowDialog() == true)
{
try
{
var info = new FileInfo(ofd.FileName);
Image = GetImageFromPath(ofd.FileName);
EditUser.Image = $"/Resource/{info.Name}";
var newPath = directory.Substring(0, directory.Length) + EditUser.Image;
if (!File.Exists(newPath))
File.Copy(ofd.FileName, newPath, true);
}
catch (Exception)
{
MessageBox.Show("Code error: Image Error", "Error", MessageBoxButton.OK, MessageBoxImage.Error);
}
}
});
}
public void CloseWin(object obj)
{
Window win = obj as Window;
win.Close();
}
private BitmapImage GetImageFromPath(string url)
{
BitmapImage img = new BitmapImage();
img.BeginInit();
img.CacheOption = BitmapCacheOption.OnLoad;
img.UriSource = new Uri(url, UriKind.Absolute);
img.EndInit();
return img;
}
public async Task EditUsers()
{
await Api.PutAsync<UserApi>(EditUser, "User");
}
public async Task GetUserId()
{
var t = SingInWindowViewModel.UsId;
var result = await Api.GetAsync<UserApi>(t, "User");
EditUser = result;
SignalChanged("EditUser");
}
}
I tried to update the data after closing the child window, but this did not solve the problem. Because the child window does not know about the parent window.
I suggest you to use event on each page objects.
Below code is minimum example for it.
ViewModel Parent Window
public class MasterWinViewModel : Notify
{
private object _curentPage;
public object CurentPage
{
get { return _curentPage; }
set
{
_curentPage = value;
SignalChanged("CurentPage");
}
}
public CustomCommand Profile { get; set; }
public MasterWinViewModel()
{
Profile = new CustomCommand(() =>
{
ProfilePageViewModel profilepagevm = new ProfilePageViewModel();
ProfilePageViewModel.OnEventFire += (sender, data) =>
{
//sender = profilepage
//data = data object from profilepage
//Do something with sent data.
}
ProfilePage profilepage = new ProfilePage();
// Set DataContext. Not sure because I don't know ProfilePage's exact implementation.
profilepage.DataContext = profilepagevm;
CurentPage = profilepage;
SignalChanged("CurentPage");
});
}
}
ProfilePageViewModel
public class ProfilePageViewModel : Notify
{
public delegate void EventDelegate(object sender, object data);
public event EventDelegate OnEventFire = null;
public ProfilePageViewModel()
{
}
public void EventFireMethod(object data)
{
//Fire Event
OnEventFire?.Invoke(this, data);
}
}
After calling EventFireMethod in ProfilePage class, callback method will be invoked.
In this way, you can send data between parent window and child page object.
Hello i have the next design problem:
Before accessing to my Controller i have a filter to check the authentication and the authorization so in order to do so i have to know the user. Until there everything is perfect, but the problem starts when i want to know the user who is log so i can do more things. Any ideas?
[AdministratorAuth("DogController")]
public class DogController : ControllerBase
{
[HttpGet]
public IAction GetDogsOfUser()
{
return Ok(dogLogic.GetDogsOfUser());
}
}
public class LoginAuth : Attribute, IActionFilter
{
public static Guid Token { get; private set; }
public void OnActionExecuted(ActionExecutedContext context)
{
}
public void OnActionExecuting(ActionExecutingContext context)
{
string headerToken = context.HttpContext.Request.Headers["Authorization"];
if (headerToken is null)
{
context.Result = new ContentResult()
{
Content = "Token is required",
};
} else
{
try
{
Guid token = Guid.Parse(headerToken);
VerifyToken(token, context);
Token = token;
} catch (FormatException)
{
context.Result = new ContentResult()
{
Content = "Invalid Token format",
};
}
}
}
private void VerifyToken(Guid token, ActionExecutingContext context)
{
using (var sessions = GetSessionLogic(context))
{
if (!sessions.IsValidToken(token))
{
context.Result = new ContentResult()
{
Content = "Invalid Token",
};
}
}
}
private ISessionLogic GetSessionLogic(ActionExecutingContext context)
{
var typeOfSessionsLogic = typeof(ISessionLogic);
return context.HttpContext.RequestServices.GetService(typeOfSessionsLogic) as ISessionLogic;
}
}
public class AdministratorAuth : LoginAuth
{
private readonly string permission;
public AdministratorAuth(string permission)
{
this.permission = permission;
}
public void OnActionExecuted(ActionExecutedContext context)
{
}
public void OnActionExecuting(ActionExecutingContext context)
{
base.OnActionExecuting(context);
string headerToken = context.HttpContext.Request.Headers["Authorization"];
Guid token = Guid.Parse(headerToken);
using (var sessions = GetSessionLogic(context))
{
if (!sessions.HasLevel(token, permission))
{
context.Result = new ContentResult()
{
Content = "The user hasn't the permission to access " + permission,
};
}
}
}
private ISessionLogic GetSessionLogic(ActionExecutingContext context)
{
var typeOfSessionsLogic = typeof(ISessionLogic);
return context.HttpContext.RequestServices.GetService(typeOfSessionsLogic) as ISessionLogic;
}
}
So let's imagine that i have this, if i want to know the dogs of the user who is log, how can i do?
You can simply use Nlog or log4net function,
or
create a model which contains
Logged = DateTime.Now,
LoginHost = Request.Host.ToString(),
LoginIP = Request.HttpContext.Connection.LocalIpAddress.ToString(),
SessionId = Request.HttpContext.Session.Id
Below is my implementation. I have written a CustomExceptionFilterAttribute which inherits from ExceptionFilterAttribute. Each error is placed inside a if and else to generate proper result. What I would like to do is create a callback function so that I can remove if and else block and error can be handled in more generic way.
public class HostedServicesController : BaseController
{
public IActioResult Index()
{
throw new NotFoundInDatabaseException("Error in Index Controller");
}
}
public class NotFoundInDatabaseException : Exception
{
public NotFoundInDatabaseException(string objectName, object objectId) :
base(message: $"No {objectName} with id '{objectId}' was found")
{
}
}
public class CustomExceptionFilterAttribute :ExceptionFilterAttribute
{
private SystemManager SysMgr { get; }
public CustomExceptionFilterAttribute(SystemManager systemManager)
{
SysMgr = systemManager;
}
public override void OnException(ExceptionContext context)
{
var le = SysMgr.Logger.NewEntry();
try
{
le.Message = context.Exception.Message;
le.AddException(context.Exception);
var exception = context.Exception;
if (exception is NotFoundInDatabaseException)
{
le.Type = LogType.ClientFaultMinor;
context.Result = new NotFoundObjectResult(new Error(ExceptionCode.ResourceNotFound, exception.Message));
}
else if (exception is ConfigurationException)
{
le.Type = LogType.ErrorMinor;
context.Result = new BadRequestObjectResult(new Error(ExceptionCode.NotAuthorised, exception.Message));
}
else
{
le.Type = LogType.ErrorSevere;
context.Result = new InternalServerErrorObjectResult(new Error(ExceptionCode.Unknown, exception.Message));
}
le.AddProperty("context.Result", context.Result);
//base.OnException(context);
}
finally
{
Task.Run(() => SysMgr.Logger.LogAsync(le)).Wait();
}
}
}
You could use a Dictionary with a custom type for this. Think of something like this:
public class ErrorHandlerData
{
public LogType LogType { get; set; }
public string ExceptionCode { get; set; } // not sure if string
}
public class CustomExceptionFilterAttribute :ExceptionFilterAttribute
{
private static Dictionary<Type, ErrorHandlerData> MyDictionary = new Dictionary<Type, ErrorHandlerData>();
static CustomExceptionFilterAttribute()
{
MyDictionary.Add(typeof(NotFoundInDatabaseException), new ErrorHandlerData
{
LogType = LogType.ClientFaultMinor,
ExceptionCode = ExceptionCode.ResourceNotFound
};
//general catch-all
MyDictionary.Add(typeof(Exception), new ErrorHandlerData
{
LogType = LogType.ErrorSevere,
ExceptionCode = ExceptionCode.Unknown
};
}
So you can then use it like this:
public override void OnException(ExceptionContext context)
{
var le = SysMgr.Logger.NewEntry();
try
{
le.Message = context.Exception.Message;
le.AddException(context.Exception);
var exception = context.Exception;
var exeptionType = exception.GetType();
var errorHandlerData = MyDictionary.ContainsKey(exceptionType) ?
MyDictionary[exceptionType] : MyDictionary[typeof(Exception)];
le.Type = errorHandlerData.LogType;
context.Result = new NotFoundObjectResult(new Error(errorHandlerData.ExceptionCode, exception.Message));
le.AddProperty("context.Result", context.Result);
}
finally
{
Task.Run(() => SysMgr.Logger.LogAsync(le)).Wait();
}
}
I have web app project and an angular 2 project.
I would like use SignalR to send message from the server.
Then I found this article about implementing it.
But I don't know how to send message to the current user.
Code for send message C#:
public class EventHub : Hub
{
public async Task Subscribe(string channel)
{
await Groups.Add(Context.ConnectionId, channel);
var #event = new ChannelEvent
{
Name = $"{Context.ConnectionId} subscribed",
Data = new
{
Context.ConnectionId,
ChannelName = channel
}
};
await Publish(#event);
}
public async Task Unsubscribe(string channel)
{
await Groups.Remove(Context.ConnectionId, channel);
var #event = new ChannelEvent
{
Name = $"{Context.ConnectionId} unsubscribed",
Data = new
{
Context.ConnectionId,
ChannelName = channel
}
};
await Publish(#event);
}
public Task Publish(ChannelEvent channelEvent)
{
Clients.Caller.OnEvent(Constants.AdminChannel, channelEvent);
return Task.FromResult(0);
}
public override Task OnConnected()
{
var #event = new ChannelEvent
{
Name = $"{Context.ConnectionId} connected",
Data = new
{
Context.ConnectionId,
}
};
Publish(#event);
return base.OnConnected();
}
public override Task OnDisconnected(bool stopCalled)
{
var #event = new ChannelEvent
{
Name = $"{Context.ConnectionId} disconnected",
Data = new
{
Context.ConnectionId,
}
};
Publish(#event);
return base.OnDisconnected(stopCalled);
}
}
public static class Constants
{
public const string AdminChannel = "admin";
public const string TaskChannel = "tasks";
}
public class ChannelEvent
{
public string Name { get; set; }
public string ChannelName { get; set; }
public DateTimeOffset Timestamp { get; set; }
public object Data
{
get { return _data; }
set
{
_data = value;
Json = JsonConvert.SerializeObject(_data);
}
}
private object _data;
public string Json { get; private set; }
public ChannelEvent()
{
Timestamp = DateTimeOffset.Now;
}
}
Then in my controller I create IhubContent
private readonly IHubContext _context = GlobalHost.ConnectionManager.GetHubContext<EventHub>();
and invoke my publish event:
private void PublishEvent(string eventName, StatusModel status)
{
_context.Clients.Group(Constants.TaskChannel).OnEvent(Constants.TaskChannel, new ChannelEvent
{
ChannelName = Constants.TaskChannel,
Name = eventName,
Data = status
});
}
But this message sent to all users. Help me to fix this issue and implement code to the send message to the current user.
The IHubContext object your are using has multiple methods, one of which is Clients, of type IHubConnectionContext.
In there you have Groups, Group, Clients & Client methods which abstract what you want to target.
In your case using:
_context.Clients.Client(connectionId).send(message);
should be working fine (you still need to propagate the connectionId though).
N.B.: send is a JS method that should be implemented on client-side.
I created a new table "TravauxDBs" using Visual Studio, but when i call PullAsync it returns count = 0, so i guess it's empty. Then i get an error "not found".
Everything works fine using basic table ToDoItems, and i use the same code for this new table.
My item mobile side :
public class TravauxDB
{
public string Id { get; set; }
[JsonProperty(PropertyName = "nomtravail")]
public string NomTravail { get; set; }
[JsonProperty(PropertyName = "voie")]
public string Voie { get; set; }
[JsonProperty(PropertyName = "pk")]
public string PK { get; set; }
[JsonProperty(PropertyName = "quantity")]
public string Quantity { get; set; }
[JsonProperty(PropertyName = "unite")]
public string Unite { get; set; }
[JsonProperty(PropertyName = "observation")]
public string Observation { get; set; }
[JsonProperty(PropertyName = "idchantier")]
public string IdChantier { get; set; }
}
Service side :
public class TravauxDB : EntityData
{
public string NomTravail { get; set; }
public string Voie { get; set; }
public string PK { get; set; }
public string Quantity { get; set; }
public string Unite { get; set; }
public string Observation { get; set; }
public string IdChantier { get; set; }
}
Activity :
public class Travaux : ActionBarActivity
{
private Android.Support.V7.Widget.SearchView _searchView3;
private ListView _listView3;
private ArrayAdapter _adapter3;
private MobileServiceClient client;
private IMobileServiceSyncTable<TravauxDB> TravauxTable;
const string applicationURL = #"myurl";
const string applicationKey = #"mykey";
const string localDbFilename = "localstore.db";
public List<string> travaux { get; private set; }
protected override async void OnCreate(Bundle savedInstanceState)
{
base.OnCreate(savedInstanceState);
SetContentView(Resource.Layout.Layout_travaux);
CurrentPlatform.Init();
client = new MobileServiceClient(applicationURL, applicationKey);
await InitLocalStoreAsync();
TravauxTable = client.GetSyncTable<TravauxDB>();
await SyncAsync();
List<string> travaux = await ItemsOnly();
_listView3 = FindViewById<ListView>(Resource.Id.listView3);
_adapter3 = new ArrayAdapter<string>(this, Android.Resource.Layout.SimpleListItem1, travaux);
_listView3.Adapter = _adapter3;
_listView3.ItemClick += _listView3_ItemClick;
}
void _listView3_ItemClick(object sender, AdapterView.ItemClickEventArgs e)
{
int pos = e.Position;
Intent b = new Intent(this, typeof(Travail));
Bundle bundle = new Bundle();
bundle.PutInt("Ntravail", pos);
b.PutExtras(bundle);
StartActivity(b);
}
public override bool OnCreateOptionsMenu(IMenu menu)
{
MenuInflater.Inflate(Resource.Menu.main, menu);
var item = menu.FindItem(Resource.Id.action_search);
var searchItem = MenuItemCompat.GetActionView(item);
_searchView3 = searchItem.JavaCast<Android.Support.V7.Widget.SearchView>();
_searchView3.QueryTextChange += (s, e) => _adapter3.Filter.InvokeFilter(e.NewText);
_searchView3.QueryTextSubmit += (s, e) =>
{
Toast.MakeText(this, "Searched for: " + e.Query, ToastLength.Short).Show();
e.Handled = true;
};
return true;
}
private void CreateAndShowDialog(Exception exception, String title)
{
CreateAndShowDialog(exception.Message, title);
}
private void CreateAndShowDialog(string message, string title)
{
Android.App.AlertDialog.Builder builder = new Android.App.AlertDialog.Builder(this);
builder.SetMessage(message);
builder.SetTitle(title);
builder.Create().Show();
}
private async Task InitLocalStoreAsync()
{
string path = Path.Combine(System.Environment.GetFolderPath(System.Environment.SpecialFolder.Personal), localDbFilename);
if (!File.Exists(path))
{
File.Create(path).Dispose();
}
var store = new MobileServiceSQLiteStore(path);
store.DefineTable<TravauxDB>();
await client.SyncContext.InitializeAsync(store);
}
private async Task SyncAsync()
{
try
{
await client.SyncContext.PushAsync();
await TravauxTable.PullAsync("alltravaux", TravauxTable.CreateQuery()); // query ID is used for incremental sync
}
catch (Java.Net.MalformedURLException)
{
CreateAndShowDialog(new Exception("There was an error creating the Mobile Service. Verify the URL"), "Error");
}
catch (Exception e)
{
CreateAndShowDialog(e, "Error");
}
}
protected override void OnActivityResult(int requestCode, Result resultCode, Intent data)
{
base.OnActivityResult(requestCode, resultCode, data);
AuthenticationAgentContinuationHelper.SetAuthenticationAgentContinuationEventArgs(requestCode, resultCode, data);
}
public async Task<List<string>> ItemsOnly()
{
try
{
List<string> travaux = await TravauxTable
.Select(tr => tr.NomTravail).ToListAsync();
return travaux;
}
catch (MobileServiceInvalidOperationException e)
{
Console.Error.WriteLine(#"ERROR {0}", e.Message);
return null;
}
}
I also added a new controller for this, just replaced ToDoItem by TravauxDB :
public class TravauxController : TableController<TravauxDB>
{
protected override void Initialize(HttpControllerContext controllerContext)
{
base.Initialize(controllerContext);
ChantierContext context = new ChantierContext();
DomainManager = new EntityDomainManager<TravauxDB>(context, Request, Services);
}
// GET tables/TodoItem
public IQueryable<TravauxDB> GetAllTravauxDB()
{
return Query();
}
// GET tables/TodoItem/48D68C86-6EA6-4C25-AA33-223FC9A27959
public SingleResult<TravauxDB> GetTravauxDB(string id)
{
return Lookup(id);
}
// PATCH tables/TodoItem/48D68C86-6EA6-4C25-AA33-223FC9A27959
public Task<TravauxDB> PatchTravauxDB(string id, Delta<TravauxDB> patch)
{
return UpdateAsync(id, patch);
}
// POST tables/TodoItem
public async Task<IHttpActionResult> PostTravauxDB(TravauxDB item)
{
TravauxDB current = await InsertAsync(item);
return CreatedAtRoute("Tables", new { id = current.Id }, current);
}
// DELETE tables/TodoItem/48D68C86-6EA6-4C25-AA33-223FC9A27959
public Task DeleteTravauxDB(string id)
{
return DeleteAsync(id);
}
}
If something is missing or you need more details let me know.
Thank you by advance.
The client class name needs to match the server controller name. So, your class should be called Travaux instead of TravauxDB. Alternatively, you can use the attribute [TableName] on the class.