SignalR Web API send message to current user - c#

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.

Related

Create a schedule for a runbook in automation with C#

So, I have been struggling with this for a while now, but I am getting a bit further each day. But now I am really stuck.
What I am trying to do:
I have an automation account with a runbook (PowerShell)
I want to create a schedule
Then I want to connect that schedule to the runbook so the runbook is executed at a specific date and time.
I want to achieve this through C# with the Management API of Azure. I found a few webpages that should give me all the information I needed, but it doesn't work.
The first one is this: https://learn.microsoft.com/en-us/rest/api/automation/schedule/create-or-update?tabs=HTTP
I get this to work and I can see the schedule after I ran the code.
The second one is this: https://learn.microsoft.com/en-us/rest/api/automation/job-schedule/create?tabs=HTTP
And here I get stuck. I get a 404 error when I execute the request and I have no idea what I am doing wrong.
The code below is far from perfect, but it's a POC and not the actual application. I want to make it work before I refactor.
This class has all the logic for the schedule:
public class ScheduleLogic
{
const string subscriptionId = "######";
const string resourceGroupName = "######";
const string automationAccountName = "######";
const string tenantId = "######";
const string clientId = "######";
const string clientSecret = "######";
const string resourceId = "https://management.azure.com/";
public string accessToken { get; set; }
// THIS ONE DOESN'T WORK!
public async Task ConnectScheduleToRunbook(RunbookSchedule runbookSchedule)
{
StringContent body = new(JsonSerializer.Serialize(runbookSchedule));
body.Headers.ContentType = new MediaTypeHeaderValue("application/json");
string url = $"https://management.azure.com/subscriptions/{subscriptionId}/resourceGroups/{resourceGroupName}/providers/Microsoft.Automation/automationAccounts/{automationAccountName}/jobSchedules/{runbookSchedule.Properties.Schedule.JobScheduleId}?api-version=2019-06-01";
await ExecuteRequest(RequestType.Put, url, body);
}
public async Task CreateSchedule(Schedule schedule)
{
if (string.IsNullOrEmpty(schedule.JobScheduleId))
throw new Exception("Job schedule ID is empty");
StringContent body = new(JsonSerializer.Serialize(schedule));
body.Headers.ContentType = new MediaTypeHeaderValue("application/json");
string url = $"https://management.azure.com/subscriptions/{subscriptionId}/resourceGroups/{resourceGroupName}/providers/Microsoft.Automation/automationAccounts/{automationAccountName}/schedules/{schedule.JobScheduleId}?api-version=2019-06-01";
await ExecuteRequest(RequestType.Put, url, body);
}
public async Task DeleteSchedule(string scheduleName)
{
string url = $"https://management.azure.com/subscriptions/{subscriptionId}/resourceGroups/{resourceGroupName}/providers/Microsoft.Automation/automationAccounts/{automationAccountName}/schedules/{scheduleName}?api-version=2019-06-01";
await ExecuteRequest(RequestType.Delete, url, null);
}
private async Task ExecuteRequest(RequestType requestType, string url, StringContent? body)
{
HttpResponseMessage response;
using (HttpClient client = new())
{
client.DefaultRequestHeaders.Add("Authorization", $"Bearer {await GetAccessToken()}");
switch (requestType)
{
case RequestType.Put:
if (body == null)
throw new Exception("Body cannot be empty");
response = await client.PutAsync(url, body);
break;
case RequestType.Delete:
response = await client.DeleteAsync(url);
break;
default:
throw new Exception("Unknown request type");
}
if (!response.IsSuccessStatusCode)
{
var content = await response.Content.ReadAsStringAsync();
throw new Exception(content);
}
}
}
private async Task<string> GetAccessToken()
{
if (!string.IsNullOrEmpty(accessToken))
return accessToken;
AuthenticationContext authContext = new("https://login.microsoftonline.com/" + tenantId);
ClientCredential clientCreds = new(clientId, clientSecret);
AuthenticationResult authResult = await authContext.AcquireTokenAsync(resourceId, clientCreds);
accessToken = authResult.AccessToken;
return accessToken;
}
}
Models:
public class RunbookSchedule
{
public RunbookScheduleProperties Properties { get; set; }
}
public class RunbookScheduleProperties
{
public Schedule Schedule { get; set; }
public Runbook Runbook { get; set; }
public object Parameters { get; set; }
}
public class Runbook
{
public string Name { get; set; }
}
public class Schedule
{
public string Name { get; set; }
[JsonIgnore]
public string JobScheduleId { get; set; }
[JsonPropertyName("properties")]
public ScheduleProperties ScheduleProperties { get; set; }
}
public class ScheduleProperties
{
public string Description { get; set; }
public DateTime StartTime { get; set; }
public DateTime ExpiryTime { get; set; }
public string Frequency { get; set; }
public object AdvancedSchedule => new { };
}
And the unit tests I use to test the logic:
public class ScheduleTests
{
private readonly string jobId = "d52004cc-b7ec-4b9b-99c1-3922492f6e1b-1";
private readonly string runbookName = "RunFunctionApp";
private readonly ScheduleLogic scheduleService;
public ScheduleTests()
{
scheduleService = new();
}
[Fact]
public async Task Should_CreateASchedule()
{
Schedule newScheduleSettings = new()
{
Name = jobId,
JobScheduleId = jobId,
ScheduleProperties = new()
{
Description = "Here is another example",
StartTime = new DateTime(2024, 03, 27, 9, 0, 0, 0),
ExpiryTime = new DateTime(2024, 03, 27, 10, 0, 0, 0),
Frequency = "OneTime"
}
};
await scheduleService.CreateSchedule(newScheduleSettings);
}
[Fact]
public async Task Should_DeleteSchedule()
{
await scheduleService.DeleteSchedule(jobId);
}
[Fact]
public async Task Should_ConnectScheduleAndRunbook()
{
await scheduleService.ConnectScheduleToRunbook(new RunbookSchedule
{
Properties = new()
{
Schedule = new() { Name = jobId, JobScheduleId = jobId },
Runbook = new() { Name = runbookName },
Parameters = new { id = 12 }
}
});
}
}
Somehow I think I am doing something stupid and the fix/answer is really simple.
I tried to mess around with the URL because I think there is a problem with that, not sure why... Just a feeling.
Google did show me some results, but those were for schedules of other services. Runbooks and automation don't seem to be very popular.
ChatGPT comes with older, obsolete solutions. Visual Studio gives a lot of green and even red lines when I try those suggestions.

Using Google Drive API with XamarinForms

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);
}
}

Conversation logging combined with AAD v1 authentication

I am having trouble with adding both my AAD1 Authentication and custom Conversation logger onto my ChatBot. One or the other works fine, but when combining the two, I get HTTP timeouts. Any assistance would be greatly appreciated. Relevant code below:
Global.asax.cs
protected void Application_Start()
{
// Adding DocumentDB endpoint and primary key
var docDbServiceEndpoint = new Uri("-----------------------------------");//REMOVED Uri for question, no issue with connection as is
var docDbKey = "--------------------------------------------"; //REMOVED Key for question, no issue with connection as is
Conversation.UpdateContainer(builder =>
{
builder.RegisterModule(new AzureModule(Assembly.GetExecutingAssembly()));
var store = new DocumentDbBotDataStore(docDbServiceEndpoint, docDbKey); // requires Microsoft.BotBuilder.Azure Nuget package
builder.RegisterType<DebugActivityLogger>().AsImplementedInterfaces().InstancePerDependency();
});
//authorization stuff
AuthSettings.Mode = ConfigurationManager.AppSettings["ActiveDirectory.Mode"];
AuthSettings.EndpointUrl = ConfigurationManager.AppSettings["ActiveDirectory.endpointUrl"];
AuthSettings.Tenant = ConfigurationManager.AppSettings["ActiveDirectory.Tenant"];
AuthSettings.RedirectUrl = ConfigurationManager.AppSettings["ActiveDirectory.RedirectUrl"];
AuthSettings.ClientId = ConfigurationManager.AppSettings["ActiveDirectory.ClientId"];
AuthSettings.ClientSecret = ConfigurationManager.AppSettings["ActiveDirectory.ClientSecret"];
GlobalConfiguration.Configure(WebApiConfig.Register);
}
AuthenticationHelper.cs
[Serializable]
public class AuthenticationHelper : IDialog<string>
{
public async Task StartAsync(IDialogContext context)
{
context.Wait(ProcessMessageAsync);
}
public async Task ProcessMessageAsync(IDialogContext context, IAwaitable<IMessageActivity> item)
{
var message = await item;
if (string.IsNullOrEmpty(await context.GetAccessToken("https://graph.microsoft.com/")))
{
//NO ACCESS TOKEN, GET IT
await context.Forward(new AzureAuthDialog("https://graph.microsoft.com"), this.ProcessAuthResultAsync, message, System.Threading.CancellationToken.None);
}
else
{
//have token
await context.Forward(new LuisAskQuestionDialog(), this.QuitMessageReceivedAsync, message, System.Threading.CancellationToken.None);
}
}
public async Task ProcessAuthResultAsync(IDialogContext context, IAwaitable<string> result)
{
var message = await result;
await context.PostAsync(message);
context.Wait(ProcessMessageAsync);
}
protected async Task QuitMessageReceivedAsync(IDialogContext context, IAwaitable<object> item)
{
var message = await item;
//StartRecordingProcess();
context.Done(message);
}
}
ChatBotLogging.cs
public class DebugActivityLogger : IActivityLogger
{
private const string EndpointUrl = "------------------------------";
private const string PrimaryKey = "------------------------------------";
private DocumentClient client;
// ADD THIS PART TO YOUR CODE
public async Task LogAsync(IActivity activity)
{
//Update this information
//What this needs to have: ConversationID, From, To, Date, Message
//Get all the texts information ready for upload;
//Get connection to table
//upload the inforamtion onto the table
//disconnect from the table
// Retrieve the storage account from the connection string.
//This Task is called to intercept messages
var fromid = activity.From.Id;
var toId = activity.Recipient.Id;
var chatMessage = activity.AsMessageActivity()?.Text;
var timeStamp = activity.Timestamp;
var conversationId = activity.Conversation.Id;
//timestamp converted to string.
string strTimeStamp = timeStamp.ToString();
try
{
this.client = new DocumentClient(new Uri(EndpointUrl), PrimaryKey);
await this.client.CreateDatabaseIfNotExistsAsync(new Database { Id = "botdb" });
await this.client.CreateDocumentCollectionIfNotExistsAsync(UriFactory.CreateDatabaseUri("botdb"), new DocumentCollection { Id = "botcollection" });
ChatLogEntity chatLog1 = new ChatLogEntity
{
TimeStamp = strTimeStamp,
ConversationId = conversationId,
FromID = fromid,
ToID = toId,
ChatMessage = chatMessage
};
await this.CreateChatDocumentIfNotExists("botdb", "botcollection", chatLog1);
}
catch (Exception ex)
{
Debug.WriteLine(ex.Message);
}
}
//entity class for demo purposes
// ADD THIS PART TO YOUR CODE
private async Task CreateChatDocumentIfNotExists(string databaseName, string collectionName, ChatLogEntity chatEntity)
{
try
{
await this.client.ReadDocumentAsync(UriFactory.CreateDocumentUri(databaseName, collectionName, chatEntity.TimeStamp));
}
catch (DocumentClientException de)
{
if (de.StatusCode == HttpStatusCode.NotFound)
{
await this.client.CreateDocumentAsync(UriFactory.CreateDocumentCollectionUri(databaseName, collectionName), chatEntity);
}
else
{
throw;
}
}
}
public class ChatLogEntity
{
[JsonProperty(PropertyName = "timestamp")]
public string TimeStamp { get; set; }
public string ConversationId { get; set; }
public string ToID { get; set; }
public string FromID { get; set; }
public string ChatMessage { get; set; }
public override string ToString()
{
return JsonConvert.SerializeObject(this);
}
}
}
The AuthBot package has been discontinued. As mentioned in the comments, You should use the BotAuth for AADv1 authentication. The BotAuth supports the state data conversation support (so you will not get the deprecated state client warnings too).

Can't read data from second table using Azure Mobile Services .NET

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.

How to Implement a Web API controller to accept chunked uploads using JQuery File Upload?

As the title states, I need some help implementing a Web API controller to accept chunked uploads using JQuery File Upload. Any help (including links to existing articles/tutorials) will be much appreciated.
First let start with the client side.
You must set the maxChunkSize option for chunked uploads. After that you need a unique identifier per file in order to identify each chunk on the server and append the corresponding chunk data to the correct file.
$('#fileupload')
.bind('fileuploadsubmit', function (e, data) {
data.headers = $.extend(data.headers,
{"X-File-Identifier": generateFileUniqueIdentifier(data)})
});
});
generateFileUniqueIdentifier = function(data){
var file=data.files[0],
var result = file.relativePath||file.webkitRelativePath||file.fileName||file.name;
return result.replace(/[^0-9a-zA-Z_-]/img,"") + "-" + i.size + "-" + $.now()
}
Now on the server side: ApiController
public class UploadController : ApiController
{
[HttpPost]
[Route("upload/{targetFolder:int}")]
[ValidateMimeMultipartContentFilter]
public async Task<IHttpActionResult> UploadDocument(int targetFolder)
{
var uploadFileService = new UploadFileService();
UploadProcessingResult uploadResult = await uploadFileService.HandleRequest(Request);
if (uploadResult.IsComplete)
{
// do other stuff here after file upload complete
return Ok();
}
return Ok(HttpStatusCode.Continue);
}
}
The service class which actually upload the file. This support chunks or a whole file.
public class UploadFileService
{
private readonly string _uploadPath;
private readonly MultipartFormDataStreamProvider _streamProvider;
public UploadFileService()
{
_uploadPath = UserLocalPath;
_streamProvider = new MultipartFormDataStreamProvider(_uploadPath);
}
#region Interface
public async Task<UploadProcessingResult> HandleRequest(HttpRequestMessage request)
{
await request.Content.ReadAsMultipartAsync(_streamProvider);
return await ProcessFile(request);
}
#endregion
#region Private implementation
private async Task<UploadProcessingResult> ProcessFile(HttpRequestMessage request)
{
if (request.IsChunkUpload())
{
return await ProcessChunk(request);
}
return new UploadProcessingResult()
{
IsComplete = true,
FileName = OriginalFileName,
LocalFilePath = LocalFileName,
FileMetadata = _streamProvider.FormData
};
}
private async Task<UploadProcessingResult> ProcessChunk(HttpRequestMessage request)
{
//use the unique identifier sent from client to identify the file
FileChunkMetaData chunkMetaData = request.GetChunkMetaData();
string filePath = Path.Combine(_uploadPath, string.Format("{0}.temp", chunkMetaData.ChunkIdentifier));
//append chunks to construct original file
using (FileStream fileStream = new FileStream(filePath, FileMode.OpenOrCreate | FileMode.Append))
{
var localFileInfo = new FileInfo(LocalFileName);
var localFileStream = localFileInfo.OpenRead();
await localFileStream.CopyToAsync(fileStream);
await fileStream.FlushAsync();
fileStream.Close();
localFileStream.Close();
//delete chunk
localFileInfo.Delete();
}
return new UploadProcessingResult()
{
IsComplete = chunkMetaData.IsLastChunk,
FileName = OriginalFileName,
LocalFilePath = chunkMetaData.IsLastChunk ? filePath : null,
FileMetadata = _streamProvider.FormData
};
}
#endregion
#region Properties
private string LocalFileName
{
get
{
MultipartFileData fileData = _streamProvider.FileData.FirstOrDefault();
return fileData.LocalFileName;
}
}
private string OriginalFileName
{
get
{
MultipartFileData fileData = _streamProvider.FileData.FirstOrDefault();
return fileData.Headers.ContentDisposition.FileName.Replace("\"", string.Empty);
}
}
private string UserLocalPath
{
get
{
//return the path where you want to upload the file
}
}
#endregion
}
The extensions over HttpRequestMessagge used to identify a chunk request
public static class HttpRequestMessageExtensions
{
public static bool IsChunkUpload(this HttpRequestMessage request)
{
return request.Content.Headers.ContentRange != null;
}
public static FileChunkMetaData GetChunkMetaData(this HttpRequestMessage request)
{
return new FileChunkMetaData()
{
ChunkIdentifier = request.Headers.Contains("X-DS-Identifier") ? request.Headers.GetValues("X-File-Identifier").FirstOrDefault() : null,
ChunkStart = request.Content.Headers.ContentRange.From,
ChunkEnd = request.Content.Headers.ContentRange.To,
TotalLength = request.Content.Headers.ContentRange.Length
};
}
}
And at the end the service response model and chunk metadata
public class FileChunkMetaData
{
public string ChunkIdentifier { get; set; }
public long? ChunkStart { get; set; }
public long? ChunkEnd { get; set; }
public long? TotalLength { get; set; }
public bool IsLastChunk
{
get { return ChunkEnd + 1 >= TotalLength; }
}
}
public class UploadProcessingResult
{
public bool IsComplete { get; set; }
public string FileName { get; set; }
public string LocalFilePath { get; set; }
public NameValueCollection FileMetadata { get; set; }
}
The MultiPartContentFilter is just an ActionFilter to validate the content (from damienbod)
public class ValidateMimeMultipartContentFilter : ActionFilterAttribute
{
public override void OnActionExecuting(HttpActionContext actionContext)
{
if (!actionContext.Request.Content.IsMimeMultipartContent())
{
throw new HttpResponseException(HttpStatusCode.UnsupportedMediaType);
}
}
public override void OnActionExecuted(HttpActionExecutedContext actionExecutedContext)
{
}
}
You can find inspiration here: ASP.NET Multiple File Upload With Drag & Drop and Progress Bar Using HTML5. An example of a chunked upload controller method starts in UploadFile. On the client, jquery file upload option maxChunkSize needs to be set according to https://github.com/blueimp/jQuery-File-Upload/wiki/Options .

Categories

Resources