I would like a simple middleware that I can use to combine multiple requests into one request and return the result as a single array response.
I don't want to use OData because its too heavy, plus I don't like it.
I have no idea how I can split one HttpContext into multiple small internal sub HttpContext's.
This is my attempt:
public static IApplicationBuilder BatchRequest(this IApplicationBuilder app)
{
app.Map("/api/batch", builder =>
{
builder.Use(async (context, next) =>
{
string[] paths = (context.Request.Query.Get("path") as StringValues?) ?? new string[] { };
Stream originalBody = context.Response.Body;
RecyclableMemoryStreamManager _recyclableMemoryStreamManager = new RecyclableMemoryStreamManager();
IEnumerable<string> responses = await paths.SelectAsync(async path =>
{
context.Request.Path = path;
MemoryStream newResponseBody = _recyclableMemoryStreamManager.GetStream();
context.Response.Body = newResponseBody;
await next.Invoke();
return RequestResponseLoggingMiddleware.ReadStreamInChunks(newResponseBody);
});
await context.Response.WriteAsync(responses.Serialize());
});
});
return app;
}
There is this example, but i am not yet sure how to use it: https://github.com/Tornhoof/HttpBatchHandler
Please be kind.
Related
I have a background service which reads & sends from a mailbox. It is created in a web ui, but after the schedule is created and mailbox set, it should run automatically, without further user prompt.
I have used the various combinations of the MSAL and both public and confidential clients (either would be acceptable as the server can maintain the client secret.
I have used the EWS client and got that working, but there is a note that the client_credentials flow won't work for IMAP/POP/SMTP.
I have a small console app working, but each time it runs, it needs to login interactively, and so long as I don't restart the application, it will keep authenticating, and I can call the AquireTokenSilently.
The Question
How can I make the MSAL save the tokens/data such that when it next runs, I can authenticate without user interaction again? I can store whatever is needed to make this work when the user authenticates, but I don't know what that should be nor how to reinstate it to make a new request, if the console app is restarted.
The Code
internal async Task<string> Test()
{
PublicClientApplication =
PublicClientApplicationBuilder.Create( "5896de31-e251-460c-9dc2-xxxxxxxxxxxx" )
.WithRedirectUri( "https://login.microsoftonline.com/common/oauth2/nativeclient" )
.WithAuthority( AzureCloudInstance.AzurePublic, ConfigurationManager.AppSettings["tenantId"] )
.Build();
//var scopes = new string[] { "email", "offline_access", "profile", "User.Read", "Mail.Read" };
var scopes = new string[] { "https://outlook.office.com/IMAP.AccessAsUser.All" };
var accounts = await PublicClientApplication.GetAccountsAsync();
var firstAccount = accounts.FirstOrDefault();
AuthenticationResult authResult;
if (firstAccount == null )
{
authResult = await PublicClientApplication.AcquireTokenInteractive( scopes ).ExecuteAsync();
}
else
{
//The firstAccount is null when the console app is run again
authResult = await PublicClientApplication.AcquireTokenSilent( scopes, firstAccount ).ExecuteAsync();
}
if(authResult == null)
{
authResult = await PublicClientApplication.AcquireTokenInteractive( scopes ).ExecuteAsync();
}
MailBee.Global.LicenseKey = "MN120-569E9E8D9E5B9E8D9EC8C4BC83D3-D428"; // (demo licence only)
MailBee.ImapMail.Imap imap = new MailBee.ImapMail.Imap();
var xOAuthkey = MailBee.OAuth2.GetXOAuthKeyStatic( authResult.Account.Username, authResult.AccessToken );
imap.Connect( "imap.outlook.com", 993 );
imap.Login( null, xOAuthkey, AuthenticationMethods.SaslOAuth2, AuthenticationOptions.None, null );
imap.SelectFolder( "INBOX" );
var count = imap.MessageCount.ToString();
return authResult.AccessToken;
}
It feels very much like a step missed, which can store the information to make subsequent requests and I would love a pointer in the right direction please.
When you create your PublicClientApplication, it provides you with the UserTokenCache.
UserTokenCache implements interface ITokenCache, which defines events to subscribe to token cache serialization requests as well as methods to serialize or de-serialize the cache at various formats.
You should create your own TokenCacheBuilder, which can store the tokens in file/memory/database etc.. and then use the events to subscribe to to token cache request.
An example of a FileTokenCacheProvider:
public abstract class MsalTokenCacheProviderBase
{
private Microsoft.Identity.Client.ITokenCache cache;
private bool initialized = false;
public MsalTokenCacheProviderBase()
{
}
public void InitializeCache(Microsoft.Identity.Client.ITokenCache tokenCache)
{
if (initialized)
return;
cache = tokenCache;
cache.SetBeforeAccessAsync(OnBeforeAccessAsync);
cache.SetAfterAccessAsync(OnAfterAccessAsync);
initialized = true;
}
private async Task OnAfterAccessAsync(TokenCacheNotificationArgs args)
{
if (args.HasStateChanged)
{
if (args.HasTokens)
{
await StoreAsync(args.Account.HomeAccountId.Identifier,
args.TokenCache.SerializeMsalV3()).ConfigureAwait(false);
}
else
{
// No token in the cache. we can remove the cache entry
await DeleteAsync<bool>(args.SuggestedCacheKey).ConfigureAwait(false);
}
}
}
private async Task OnBeforeAccessAsync(TokenCacheNotificationArgs args)
{
if (!string.IsNullOrEmpty(args.SuggestedCacheKey))
{
byte[] tokenCacheBytes = await GetAsync<byte[]>(args.SuggestedCacheKey).ConfigureAwait(false);
args.TokenCache.DeserializeMsalV3(tokenCacheBytes, shouldClearExistingCache: true);
}
}
protected virtual Task OnBeforeWriteAsync(TokenCacheNotificationArgs args)
{
return Task.CompletedTask;
}
public abstract Task StoreAsync<T>(string key, T value);
public abstract Task DeleteAsync<T>(string key);
public abstract Task<T> GetAsync<T>(string key);
public abstract Task ClearAsync();
}
And the MsalFileTokenCacheProvider:
public sealed class MsalFileTokenCacheProvider : MsalTokenCacheProviderBase
{
private string basePath;
public MsalFileTokenCacheProvider(string basePath)
{
this.basePath = basePath;
}
public override Task ClearAsync()
{
throw new NotImplementedException();
}
public override Task DeleteAsync<T>(string key)
{
if (string.IsNullOrEmpty(key))
{
throw new ArgumentException("Key MUST have a value");
}
string path = Path.Combine(basePath, key + ".json");
if (File.Exists(path))
File.Delete(path);
return Task.FromResult(true);
}
public override Task<T> GetAsync<T>(string key)
{
if (string.IsNullOrEmpty(key))
{
throw new ArgumentException("Key MUST have a value");
}
string path = Path.Combine(basePath, key + ".json");
if (File.Exists(path))
{
T value = JsonConvert.DeserializeObject<T>(File.ReadAllText(path));
return Task.FromResult(value);
}
else
return Task.FromResult(default(T));
}
public override Task StoreAsync<T>(string key, T value)
{
string contents = JsonConvert.SerializeObject(value);
string path = Path.Combine(basePath, key + ".json");
File.WriteAllText(path, contents);
return Task.FromResult(value);
}
}
So based on your code you will have:
PublicClientApplication =
PublicClientApplicationBuilder.Create( "5896de31-e251-460c-9dc2-xxxxxxxxxxxx" )
.WithRedirectUri( "https://login.microsoftonline.com/common/oauth2/nativeclient" )
.WithAuthority( AzureCloudInstance.AzurePublic, ConfigurationManager.AppSettings["tenantId"] )
.Build();
MsalFileTokenCacheProvider cacheProvider = new MsalFileTokenCacheProvider("TokensFolder");
cacheProvider.InitializeCache(PublicClientApplication.UserTokenCache);
//var scopes = new string[] { "email", "offline_access", "profile", "User.Read", "Mail.Read" };
var scopes = new string[] { "https://outlook.office.com/IMAP.AccessAsUser.All" };
// when you call the below code, the PublicClientApplication will use your token cache
//provider in order to get the required Account. You should also use the
//PublicClientApplication.GetAccountAsync(key) which will use the token cache provider for
//the specific account that you want to get the token. If there is an account you could
//just call the AcquireTokenSilent method. The acquireTokenSilent method will take care of the token expiration and will refresh if needed.
//Please bare in mind that in some circumstances the AcquireTokenSilent method will fail and you will have to use the AcquireTokenInteractive method again. //Example of this would be when the user changes password, or has removed the access to your Application via their Account.
var accounts = await PublicClientApplication.GetAccountsAsync();
var firstAccount = accounts.FirstOrDefault();
Please refer to the following documentation from Microsoft.
https://learn.microsoft.com/en-us/azure/active-directory/develop/msal-net-token-cache-serialization
I have Generated Microsoft Graph app in ASP.NET MVC platform, that I have downloaded from Microsoft Graph site. I need to access the shared mail folder not sure exactly how can I get that?? In the following code I can access my mailFolder but not shared mailfolder!
public static async Task<IEnumerable<MailFolder>> GetMailFolderAsync()
{
var graphClient = GetAuthenticatedClient();
var mailFolder = await graphClient.Me.MailFolders.Request().GetAsync();
var sharedMailFolder = await graphClient.Users.Request().GetAsync();
return mailFolder;
}
Also, I want to know in above code where I can pass the parameter to access next page or all pages??
private static GraphServiceClient GetAuthenticatedClient()
{
return new GraphServiceClient(
new DelegateAuthenticationProvider(
async (requestMessage) =>
{
string signedInUserId = ClaimsPrincipal.Current.FindFirst(ClaimTypes.NameIdentifier).Value;
SessionTokenStore tokenStore = new SessionTokenStore(signedInUserId,
new HttpContextWrapper(HttpContext.Current));
var idClient = new ConfidentialClientApplication(
appId, redirectUri, new ClientCredential(appSecret),
tokenStore.GetMsalCacheInstance(), null);
var accounts = await idClient.GetAccountsAsync();
var result = await idClient.AcquireTokenSilentAsync(
graphScopes.Split(' '), accounts.FirstOrDefault());
requestMessage.Headers.Authorization =
new AuthenticationHeaderValue("Bearer", result.AccessToken);
}));
I think it is not possible to access shared folders I am investigating as well. In regards to the question of getting pages, as soon as you get the first request
public static async Task<IEnumerable<MailFolder>> GetMailFolderAsync()
{
var graphClient = GetAuthenticatedClient();
var mailFolder = await graphClient.Me.MailFolders.Request().GetAsync();
var sharedMailFolder = await graphClient.Users.Request().GetAsync();
return mailFolder;
}
then you can review for example, mailFolder.NextPageRequest, if it is not null then you can request it by doing mailFolder.NextPageRequest.GetAsync() and you can use it as a loop conditional
while(mailfoldersCollection != null) {
// Do your stuff with items within for(var folder in mailfoldersCollection) {}
// when read all items in CurrentPage then
if (mailFolder.NextPageRequest != null) {
mailfoldersCollection = await mailFolder.NextPageRequest.GetAsync();
}
hope it works for you!
I want to make to multiple requests to same server in an optimal way. So I have
Future<List<Item>> getAllItems() async {
var client = new http.Client();
List<String> itemsIds = ['1', '2', '3']; //different ids
List<Item> itemList = [];
for (var item in itemsIds) {
//make call to server eg: 'sampleapi/1/next' etc
await client.get('sampleapi/' + item + '/next').then((response) {
//Do some processing and add to itemList
});
}
client.close();
return itemList;
}
Now, the api calls are made one after other. But the api calls are independent of each other. Whats the best way to implement so as to avoid the async await hell?
You can use Future.wait(...) to wait for a set of Futures to complete:
Future<List<Item>> getAllItems() async {
var client = new http.Client();
List<String> itemsIds = ['1', '2', '3']; //different ids
return Future.wait<Item>(['1', '2', '3'].map((item) =>
client.get('sampleapi/' + item + '/next').then((response) {
//Do some processing and add to itemList
return foo; // some Item that is the result of this request
});
);
}
See also https://api.dartlang.org/stable/1.24.3/dart-async/Future/wait.html
Günter beat me to it by a couple minutes, but I've already typed it out so here's a slight alternative which would also work and avoids using 'then' completely.
Future<List<Item>> getAllItems() async {
var client = new Client();
List<String> itemsIds = ['1', '2', '3']; //different ids
List<Response> list = await Future.wait(itemsIds.map((itemId) => client.get('sampleapi/$itemId/next')));
return list.map((response){
// do processing here and return items
return new Item();
}).toList();
}
Can someone help me on this - in the code below the option 3 works fine for me, but that is a SYNC call, the Option 4 never returns which is true ASYNC, I need option 4 to work (the main difference is using AWAIT syntax).
// CODE from Controller within MVC APP
IRBVer01AdminSite.Data.ApiClient apic = new Data.ApiClient();
IRBVer01AdminSite.Models.Producer prd = new IRBVer01AdminSite.Models.Producer();
// opt 3 - Make a HTTP call to API DLL - BUT this one is Syncronous
source = null;
source = apic.RunAsync("http://localhost:56099/", string.Concat("api/producers/", id.ToString().Trim())).Result;
producer = null;
destination = null;
destination = Mapper.Map<IRBVer01CodeFirst.IRBVer01Domain.Producer>(source);
producer = destination;
if (producer == null)
return HttpNotFound();
//
// opt 4 - Make Http call to API DLL - Syncronous method (FAILS SO FAR)
source = null;
source = apic.GetAsyncData("http://localhost:56099/", string.Concat("api/producers/", id.ToString().Trim())).Result;
producer = null;
destination = null;
destination = Mapper.Map<IRBVer01CodeFirst.IRBVer01Domain.Producer>(source);
producer = destination;
if (producer == null)
return HttpNotFound();
//
// CODE FROM ApiClient CLASS within MVC APP
using System;
using System.Net.Http;
using System.Net.Http.Headers;
using System.Threading.Tasks;
using Newtonsoft.Json;
// called on OPT 3
public async Task<IRBVer01Api.Models.Producer> RunAsync(string urlBase, string urlPath)
{
IRBVer01Api.Models.Producer producer = null;
using (var client = new HttpClient())
{
client.BaseAddress = new Uri(urlBase);
client.DefaultRequestHeaders.Accept.Clear();
client.DefaultRequestHeaders.Accept.Add(new MediaTypeWithQualityHeaderValue("application/json"));
var response = client.GetStringAsync(urlPath).Result; // No.1 - This is not SYNC, there is no AWAIT syntax used
producer = Task.Factory.StartNew(() => JsonConvert.DeserializeObject<IRBVer01Api.Models.Producer>(response)).Result;
}
return producer;
}
// called on OPT 4
public async Task<IRBVer01Api.Models.Producer> GetAsyncData(string urlBase, string urlPath)
{
IRBVer01Api.Models.Producer producer = null;
using (var client = new HttpClient())
{
client.BaseAddress = new Uri(urlBase);
client.DefaultRequestHeaders.Accept.Clear();
client.DefaultRequestHeaders.Accept.Add(new MediaTypeWithQualityHeaderValue("application/json"));
var response = await client.GetStringAsync(urlPath); // No.1 - This is ASYNC Version but comipler never comes back
//No.1 - for a moment forget about whats happening on the next syntax, the last line is not coming back... EVER
//producer = Task.Factory.StartNew(() => JsonConvert.DeserializeObject<IRBVer01Api.Models.Producer>(response)).Result;
}
return producer;
}
//
It has been decided by the ASP.NET Web API team to use the JSON.NET library for model binding JSON data. However, "normal" MVC controllers still use the inferior JsonDataContractSerializer. This causes issues with parsing dates, and is causing me much headache.
See this for reference:
http://www.devcurry.com/2013/04/json-dates-are-different-in-aspnet-mvc.html
The author chooses to solve the issue in the Knockout layer on the client. But I would prefer to solve this by using the same JSON.NET model binder in MVC controllers as in Web API controllers.
How do I substitute a different JSON model binder into ASP.NET MVC? Specifically, the JSON.NET library. Using the same model binder from Web API would be ideal if possible.
I have done this, and also heavily customized the serialization that Json.NET is doing, by:
Replace the default formatter in global.asax.cs, Application_Start:
GlobalConfiguration.Configuration.Formatters.Remove(GlobalConfiguration.Configuration.Formatters.JsonFormatter);
GlobalConfiguration.Configuration.Formatters.Add(new CustomJsonMediaTypeFormatter());
And my CustomJsonMediaTypeFormatter is:
public static class CustomJsonSettings
{
private static JsonSerializerSettings _settings;
public static JsonSerializerSettings Instance
{
get
{
if (_settings == null)
{
var settings = new JsonSerializerSettings();
// Must convert times coming from the client (always in UTC) to local - need both these parts:
settings.Converters.Add(new IsoDateTimeConverter { DateTimeStyles = System.Globalization.DateTimeStyles.AssumeUniversal }); // Critical part 1
settings.DateTimeZoneHandling = DateTimeZoneHandling.Local; // Critical part 2
// Skip circular references
settings.ReferenceLoopHandling = ReferenceLoopHandling.Ignore;
// Handle special cases in json (self-referencing loops, etc)
settings.ContractResolver = new CustomJsonResolver();
_settings = settings;
}
return _settings;
}
}
}
public class CustomJsonMediaTypeFormatter : MediaTypeFormatter
{
public JsonSerializerSettings _jsonSerializerSettings;
public CustomJsonMediaTypeFormatter()
{
_jsonSerializerSettings = CustomJsonSettings.Instance;
// Fill out the mediatype and encoding we support
SupportedMediaTypes.Add(new MediaTypeHeaderValue("application/json"));
SupportedEncodings.Add(new UTF8Encoding(false, true));
}
public override bool CanReadType(Type type)
{
return true;
}
public override bool CanWriteType(Type type)
{
return true;
}
public override Task<object> ReadFromStreamAsync(Type type, Stream stream, HttpContent content, IFormatterLogger formatterLogger)
{
// Create a serializer
JsonSerializer serializer = JsonSerializer.Create(_jsonSerializerSettings);
// Create task reading the content
return Task.Factory.StartNew(() =>
{
using (StreamReader streamReader = new StreamReader(stream, SupportedEncodings.First()))
{
using (JsonTextReader jsonTextReader = new JsonTextReader(streamReader))
{
return serializer.Deserialize(jsonTextReader, type);
}
}
});
}
public override Task WriteToStreamAsync(Type type, object value, Stream stream, HttpContent content, TransportContext transportContext)
{
// Create a serializer
JsonSerializer serializer = JsonSerializer.Create(_jsonSerializerSettings);
// Create task writing the serialized content
return Task.Factory.StartNew(() =>
{
using (StreamWriter streamWriter = new StreamWriter(stream, SupportedEncodings.First()))
{
using (JsonTextWriter jsonTextWriter = new JsonTextWriter(streamWriter))
{
serializer.Serialize(jsonTextWriter, value);
}
}
});
}
}
And finally, the CustomJsonResolver:
public class CustomJsonResolver : DefaultContractResolver
{
protected override IList<JsonProperty> CreateProperties(Type type, Newtonsoft.Json.MemberSerialization memberSerialization)
{
var list = base.CreateProperties(type, memberSerialization);
// Custom stuff for my app
if (type == typeof(Foo))
{
RemoveProperty(list, "Bar");
RemoveProperty(list, "Bar2");
}
return list;
}
private void RemoveProperty(IList<JsonProperty> list, string propertyName)
{
var rmc = list.FirstOrDefault(x => x.PropertyName == propertyName);
if (rmc != null)
{
list.Remove(rmc);
}
}
}
The JsonNetValueProviderFactory proposed here works better than the others I've tried (I had issues with arrays using Greg Ennis' one for example). This link also propose a solution to return Json from an action.