Entity Framework Null Reference When Adding New Record - entity-framework-4

I currently have a project that uses Entity Framework 4.1 for logging to a database so we can track the web application which is deployed on more than one web server. I built it using Scott Gu's Code First solution.
So, I have code like this:
logging.Logs.Add(newLog);
which is sometimes throwing this error:
System.NullReferenceException : Object reference not set to an
instance of an object. at
System.Data.Objects.ObjectStateManager.DetectConflicts(IList1
entries) at System.Data.Objects.ObjectStateManager.DetectChanges() at
System.Data.Entity.Internal.Linq.InternalSet1.ActOnSet(Action action,
EntityState newState, Object entity, String methodName) at
System.Data.Entity.Internal.Linq.InternalSet1.Add(Object entity) at
System.Data.Entity.DbSet1.Add(TEntity entity)
Most of the time it works fine, but it will hiccup now and then. Is there a best practice I should be aware of when I have more than one server using this code to access/write to the same database?
The approach used right now is that each request causes the system to add several new log objects into the collection and then save a grouping of them, rather than save each individual log record. Here's an outline of my class.
public class LoggingService : ILoggingService
{
Logging.Model.MyLogging logging;
public LoggingService()
{
InitializeLog();
}
/// <summary>
/// Save any pending log changes (only necessary if using the Add methods)
/// </summary>
public void SaveChanges()
{
//ensure that logging is not null
InitializeLog();
logging.SaveChanges();
}
#region Private Methods
private void InitializeLog()
{
if (logging == null)
logging = new Model.MyLogging();
}
private void Log(Level level, int sourceId, string message, bool save, int? siteId = null, int? epayCustomerId = null,
string sessionId = null, int? eventId = null, Exception exception = null)
{
if (sourceId == 0)
throw new ArgumentNullException("sourceId", "The Source Id cannot be null and must be valid.");
var source = (from s in logging.Sources
where s.SourceId == sourceId
select s).FirstOrDefault();
if (source == null)
throw new ArgumentNullException("sourceId", String.Format("No valid source found with Id [{0}].", sourceId));
if (eventId.HasValue)
{
if (eventId.Value > 0)
{
var code = (from e in logging.Events
where e.EventId == eventId.Value
select e).FirstOrDefault();
//if event id was passed in but no event exists, create a "blank" event
if (code == null)
{
Event newCode = new Event()
{
EventId = eventId.Value,
Description = "Code definition not specified."
};
InitializeLog();
logging.Events.Add(newCode);
logging.SaveChanges();
}
}
}
var newLog = new Log()
{
Created = DateTime.Now,
Message = message,
Source = source,
Level = level,
EventId = eventId,
SessionId = sessionId,
SiteId = siteId,
MachineName = System.Environment.MachineName,
};
if (exception != null)
newLog.Exception = String.Format("{0}{1}{2}{1}", exception.Message, Environment.NewLine, exception.StackTrace);
//ensure that the logging is not null
InitializeLog();
logging.Logs.Add(newLog);
if (save)
{
logging.SaveChanges();
}
}
#endregion
}
I am using IoC with StructureMap, and I did not initialize this class as a singleton.
For<ILoggingService>().Use<LoggingService>();
And my context class looks like this:
internal class MyLogging : DbContext
{
public DbSet<Source> Sources { get; set; }
public DbSet<Event> Events { get; set; }
public DbSet<Log> Logs { get; set; }
/// <summary>
/// DO NOT ADD ITEMS TO THIS COLLECTION
/// </summary>
public DbSet<LogArchive> LogArchives { get; set; }
protected override void OnModelCreating(DbModelBuilder modelBuilder)
{
Database.SetInitializer(new MyDbContextInitializer());
modelBuilder.Entity<Event>().Property(p => p.EventId)
.HasDatabaseGeneratedOption(DatabaseGeneratedOption.None);
modelBuilder.Entity<Source>().Property(p => p.SourceId)
.HasDatabaseGeneratedOption(DatabaseGeneratedOption.None);
modelBuilder.Entity<LogArchive>().Property(p => p.LogId)
.HasDatabaseGeneratedOption(DatabaseGeneratedOption.None);
base.OnModelCreating(modelBuilder);
}
//public class MyDbContextInitializer : DropCreateDatabaseIfModelChanges<MyLogging>
public class MyDbContextInitializer : CreateDatabaseIfNotExists<MyLogging>
{
protected override void Seed(MyLogging dbContext)
{
// seed data
base.Seed(dbContext);
}
}
}
I'm probably doing something obviously wrong, but I just don't see it.
EDIT:
As requested, here is a sample of how I call the logging service code. This particular method is logging information related to the HTTP Request. I append the the log items in one try catch and save in a separate try catch so if there is an issue it will at least save the additions that took. The handler is another service injected into this class through IoC that sends the details of the error to me via e-mail.
A single post to the server could log as many as 50-70 separate details separated into chunks of 10-15 (http request, data sent to a web service, result of the web service call, response back to the client), which is why I want to add a grouping and then save the grouping, rather than open and close a connection with each individual item.
public void LogHttpPostStart(HttpPostRequest request)
{
try
{
//if no session is set, use the ASP.NET session
request.SessionId = GetSessionId(request.SessionId);
int eventId = (int)Model.Enums.Logging.Event.SubmittedByClient;
var current = HttpContext.Current;
if (current != null)
{
logService.AddDebug((int)request.Source, String.Format("{0} HTTP Request Details {0}", Header2Token.ToString().PadRight(HeaderTokenCount, Header2Token)),
siteId: request.SiteId, epayCustomerId: request.EPayCustomerId, sessionId: request.SessionId,
eventId: eventId);
//Server Information
logService.AddDebug((int)request.Source, String.Format("Machine Name: {0}", current.Server.MachineName),
siteId: request.SiteId, epayCustomerId: request.EPayCustomerId, sessionId: request.SessionId,
eventId: eventId);
//User Information
logService.AddDebug((int)request.Source, String.Format("User Host Address: {0}", current.Request.UserHostAddress),
siteId: request.SiteId, epayCustomerId: request.EPayCustomerId, sessionId: request.SessionId,
eventId: eventId);
logService.AddDebug((int)request.Source, String.Format("User Host Name: {0}", current.Request.UserHostName),
siteId: request.SiteId, epayCustomerId: request.EPayCustomerId, sessionId: request.SessionId,
eventId: eventId);
//Browser Information
if (current.Request.Browser != null)
{
logService.AddDebug((int)request.Source, String.Format("Browser: {0}", current.Request.Browser.Browser),
siteId: request.SiteId, epayCustomerId: request.EPayCustomerId, sessionId: request.SessionId,
eventId: eventId);
logService.AddDebug((int)request.Source, String.Format("Browser Version: {0}", current.Request.Browser.Version),
siteId: request.SiteId, epayCustomerId: request.EPayCustomerId, sessionId: request.SessionId,
eventId: eventId);
logService.AddDebug((int)request.Source, String.Format("User Agent: {0}", current.Request.UserAgent),
siteId: request.SiteId, epayCustomerId: request.EPayCustomerId, sessionId: request.SessionId,
eventId: eventId);
logService.AddDebug((int)request.Source, String.Format("Is Mobile Device: {0}", current.Request.Browser.IsMobileDevice.ToString()),
siteId: request.SiteId, epayCustomerId: request.EPayCustomerId, sessionId: request.SessionId,
eventId: eventId);
if (current.Request.Browser.IsMobileDevice)
{
logService.AddDebug((int)request.Source, String.Format("Mobile Device Manufacturer: {0}", current.Request.Browser.MobileDeviceManufacturer),
siteId: request.SiteId, epayCustomerId: request.EPayCustomerId, sessionId: request.SessionId,
eventId: eventId);
logService.AddDebug((int)request.Source, String.Format("Mobile Device Model: {0}", current.Request.Browser.MobileDeviceModel),
siteId: request.SiteId, epayCustomerId: request.EPayCustomerId, sessionId: request.SessionId,
eventId: eventId);
}
logService.AddDebug((int)request.Source, String.Format("Platform: {0}", current.Request.Browser.Platform),
siteId: request.SiteId, epayCustomerId: request.EPayCustomerId, sessionId: request.SessionId,
eventId: eventId);
logService.AddDebug((int)request.Source, String.Format("Cookies Enabled: {0}", current.Request.Browser.Cookies.ToString()),
siteId: request.SiteId, epayCustomerId: request.EPayCustomerId, sessionId: request.SessionId,
eventId: eventId);
logService.AddDebug((int)request.Source, String.Format("Frames Enabled: {0}", current.Request.Browser.Frames.ToString()),
siteId: request.SiteId, epayCustomerId: request.EPayCustomerId, sessionId: request.SessionId,
eventId: eventId);
if (current.Request.Browser.JScriptVersion != null)
{
logService.AddDebug((int)request.Source, String.Format("Javascript Version: {0}", current.Request.Browser.JScriptVersion.ToString()),
siteId: request.SiteId, epayCustomerId: request.EPayCustomerId, sessionId: request.SessionId,
eventId: eventId);
}
}
logService.AddDebug((int)request.Source, String.Format("{0} End HTTP Request Details {0}", Header2Token.ToString().PadRight(HeaderTokenCount, Header2Token)),
siteId: request.SiteId, epayCustomerId: request.EPayCustomerId, sessionId: request.SessionId,
eventId: eventId);
}
}
catch (Exception ex)
{
handler.HandleError(true, ex);
}
try
{
logService.SaveChanges();
}
catch (Exception ex)
{
handler.HandleError(true, ex);
}
}

The first problem is that you are keeping open your DbContext during the application. The best practice is to only instantiate it when needed and then dispose it. But the bigger problem here is that you use your LoggingService in a multithreaded enviroment (if I understood it correctly) but DbContext is NOT thread safe. See this SO question and also the comments on ScottGu's post (search for the word thread). So you should keep your log entries around in a thread safe way somewhere else not in the DbContext and only open a DbContext when you want to access the DB.
+1 Instead of the InitializeLog() method check out System.Lazy<T>

Could be a bug in EF, could be a bug in your code, hard to tell. If you post your definitions of your entity classes maybe we can reproduce the problem otherwise it's really hard to see it. You could also try it by isolating your code and run the logging in an infinite loop with pseudo-random data until it happens again.
Few things though, if I may. You are a bit paranoid about the context being initialised in your logging service. If you initialise it in the constructor there is no way for it not to be initialised later. As you already are using a container why don't you have the context as a parameter to the constructor and let the container inject it for you. Your class is a Control-Freak(anti-pattern) basically.
public class LoggingService : ILoggingService
{
Logging.Model.MyLogging logging;
public LoggingService(MyLogging logging)
{
// check for null here
this.logging = logging;
//no further null checks
}
....
}
Further when you register the your components with the container set their lifecycle to be http request scoped.
For<ILoggingService>().HttpContextScoped().Use<LoggingService>();
Don't forget to destroy the objects at the end of request if you do this:
protected void Application_EndRequest()
{
ObjectFactory.ReleaseAndDisposeAllHttpScopedObjects();
}
This way you get fresh set of objects for every request that also get properly disposed at the end.

Related

ASP.Net 6 Core CSOM ExecuteQuery when getting a non-available user in SPO

In asp.net core 6 and CSOM library, I'm trying add a permission to a SPO file as following code.
public async Task<IActionResult> AddPermission(Guid guid, String[] emailList)
{
using (var authenticationManager = new AuthenticationManager())
using (var context = authenticationManager.GetContext(_site, _user, _securePwd))
{
File file= context.Web.GetFileById(guid);
SetFilePermission(context, file, emailList);
file.ListItemAllFields.SystemUpdate();
context.Load(file.ListItemAllFields);
await context.ExecuteQueryAsync();
return NoContent();
}
}
private static void SetFilePermission(ClientContext context, File file, string[] emailList)
{
if (emailList != null)
{
file.ListItemAllFields.BreakRoleInheritance(true, false);
var role = new RoleDefinitionBindingCollection(context);
role.Add(context.Web.RoleDefinitions.GetByType(permissionLevel));
foreach (string email in emailList)
{
User user = context.Web.SiteUsers.GetByEmail(email);
file.ListItemAllFields.RoleAssignments.Add(user, role);
}
}
}
This work successfully if only the user is available in SPO, or exception occurs. To avoid non-available user exception, I tried to move Load() and ExecuteQuery() to SetFilePermission method.
public async Task<IActionResult> AddPermission(Guid guid, String[] emailList)
{
using (var authenticationManager = new AuthenticationManager())
using (var context = authenticationManager.GetContext(_site, _user, _securePwd))
{
File file= context.Web.GetFileById(guid);
SetFilePermission(context, file, emailList);
// file.ListItemAllFields.SystemUpdate();
// context.Load(file.ListItemAllFields);
// await context.ExecuteQueryAsync();
return NoContent();
}
}
private static void SetFilePermission(ClientContext context, File file, string[] emailList)
{
if (emailList != null)
{
file.ListItemAllFields.BreakRoleInheritance(true, false);
var role = new RoleDefinitionBindingCollection(context);
role.Add(context.Web.RoleDefinitions.GetByType(permissionLevel));
foreach (string email in emailList)
{
User user = context.Web.SiteUsers.GetByEmail(email);
file.ListItemAllFields.RoleAssignments.Add(user, role);
// Move load and executequery method to here.
file.ListItemAllFields.SystemUpdate();
context.Load(file.ListItemAllFields);
context.ExecuteQueryAsync();
}
}
}
Suddenly, exception disappear even though the user is not available in SPO!
But other available emails in emailList also fail to add permission, just result in return NoContent eventually. Does anyone know the myth behind the process and explain it to me?

Firebase Delete User who signed it with apple correclty

I have implemented the Sign-In-With-Apple with Firebase. And I also have the functionality to delete a user. This is what I do:
static Future<bool> deleteUser(BuildContext context) async {
try {
await BackendService().deleteUser(
context,
);
await currentUser!.delete(); // <-- this actually deleting the user from Auth
Provider.of<DataProvider>(context, listen: false).reset();
return true;
} on FirebaseException catch (error) {
print(error.message);
AlertService.showSnackBar(
title: 'Fehler',
description: error.message ?? 'Unbekannter Fehler',
isSuccess: false,
);
return false;
}
}
As you can see I delete all the users data and finally the user himself from auth.
But Apple still thinks I am using the App. I can see it inside my Settings:
Also when trying to sign in again with apple, it acts like I already have an account. But I just deleted it and there is nothing inside Firebase that says that I still have that account?
How can I completely delete an Apple user from Firebase? What am I missing here?
Apple and some other 3rd party identity provider do not provide APIs to do so commonly.
Access to those data may lead to privacy issue, for e.g., a malicious app can remove the authorization information after access to user profile.
But if you want to do a "graceful" logout, you can ask your users to logout from iOS Settings, and listen to the server-to-server notification for revoking.
Although users account has been deleted on firebase it has not been removed from Apple's system. At the time of writing firebase SDK for Apple is still working on this feature git hub issue (Planned for Q4 2022 or Q1 2023), as flutter and react native are probably dependant on base SDK a custom implementation is needed until this is available.
According to Apple, to completely remove users Apple account you should obtain Apple's refresh token using generate_tokens API and then revoke it using revoke_tokens API.
High level description:
Client side (app): Obtain Apple authorization code.
Send authorization code to your server.
Server side: Use Apples p8 secret key to create jwt token. Jwt token will be used for authenticating requests towards Apple's API
Server side: Trade authorization code for refresh_token (see first link above)
Server side: Revoke refresh_token (see second link above)
Detailed description:
https://stackoverflow.com/a/72656672/6357154
.NET implantation of the server side process.
Assumptions:
_client is a HttpClient registered in DI contrainer with base url from Apple docs posted above
AppleClientOptions contains the same values used for Apple setup on firebase.
/// <summary>
/// Gets apple refresh token
/// SEE MORE: https://developer.apple.com/documentation/sign_in_with_apple/generate_and_validate_tokens
/// </summary>
/// <param name="jwtToken"></param>
/// <param name="authorizationCode"></param>
/// <returns></returns>
public async Task<string> GetTokenFromApple(string jwtToken, string authorizationCode)
{
IEnumerable<KeyValuePair<string, string>> content = new[]
{
new KeyValuePair<string, string>("client_id", _appleClientOptions.ClientId),
new KeyValuePair<string, string>("client_secret", jwtToken),
new KeyValuePair<string, string>("code", authorizationCode),
new KeyValuePair<string, string>("grant_type", "authorization_code"),
};
var encodedContent = new FormUrlEncodedContent(content);
var response = await _client.PostAsync("auth/token", encodedContent);
var responseAsString = await response.Content.ReadAsStringAsync();
if (response.IsSuccessStatusCode)
{
var appleTokenResponse = JsonConvert.DeserializeObject<AppleTokenResponse>(responseAsString);
return appleTokenResponse.refresh_token;
}
_logger.LogError($"GetTokenFromApple failed: {responseAsString}");
return null;
}
/// <summary>
/// Revokes apple refresh token
/// SEE MORE: https://developer.apple.com/documentation/sign_in_with_apple/generate_and_validate_tokens
/// </summary>
/// <param name="jwtToken"></param>
/// <param name="refreshToken"></param>
/// <returns></returns>
public async Task<bool> RevokeToken(string jwtToken, string refreshToken)
{
IEnumerable<KeyValuePair<string, string>> content = new[]
{
new KeyValuePair<string, string>("client_id", _appleClientOptions.ClientId),
new KeyValuePair<string, string>("client_secret", jwtToken),
new KeyValuePair<string, string>("token", refreshToken),
new KeyValuePair<string, string>("token_type_hint", "refresh_token"),
};
var response = await _client.PostAsync("auth/revoke", new FormUrlEncodedContent(content));
return response.IsSuccessStatusCode;
}
private string GenerateAppleJwtTokenLinux()
{
var epochNow = (int) DateTime.UtcNow.Subtract(new DateTime(1970, 1, 1)).TotalSeconds;
var (payload, extraHeaders) = CreateJwtPayload(
epochNow,
_appleClientOptions.TeamId,
_appleClientOptions.ClientId,
_appleClientOptions.KeyId);
var privateKeyCleaned = Base64Decode(_appleClientOptions.PrivateKey)
.Replace("-----BEGIN PRIVATE KEY-----", string.Empty)
.Replace("-----END PRIVATE KEY-----", string.Empty)
.Replace("\r\n", string.Empty)
.Replace("\r\n", string.Empty);
var bytes = Convert.FromBase64String(privateKeyCleaned);
using var ecDsaKey = ECDsa.Create();
ecDsaKey!.ImportPkcs8PrivateKey(bytes, out _);
return Jose.JWT.Encode(payload, ecDsaKey, JwsAlgorithm.ES256, extraHeaders);
}
private static (Dictionary<string, object> payload, Dictionary<string, object> extraHeaders) CreateJwtPayload(
int epochNow,
string teamId,
string clientId,
string keyId)
{
var payload = new Dictionary<string, object>
{
{"iss", teamId},
{"iat", epochNow},
{"exp", epochNow + 12000},
{"aud", "https://appleid.apple.com"},
{"sub", clientId}
};
var extraHeaders = new Dictionary<string, object>
{
{"kid", keyId},
{"alg", "ES256"}
};
return (payload, extraHeaders);
}
/// <summary>
/// https://developer.apple.com/documentation/sign_in_with_apple/tokenresponse
/// </summary>
public class AppleTokenResponse
{
public string access_token { get; set; }
public string expires_in { get; set; }
public string id_token { get; set; }
public string refresh_token { get; set; }
public string token_type { get; set; }
}
public class AppleClientOptions
{
public string TeamId { get; set; }
public string ClientId { get; set; }
public string KeyId { get; set; }
public string PrivateKey { get; set; }
}
public async Task<bool> DeleteUsersAccountAsync(string appleAuthorizationCode)
{
// Get jwt token:
var jwtToken = _appleClient.GenerateAppleJwtTokenLinux(); // Apple client is code form above, registered in DI.
// Get refresh token from authorization code:
var refreshToken = await _appleClient.GetTokenFromApple(jwtToken, appleAuthorizationCode);
if (string.IsNullOrEmpty(refreshToken)) return false;
// Delete token:
var isRevoked = await _appleClient.RevokeToken(jwtToken, refreshToken);
_logger.LogInformation("Deleted apple tokens for {UserId}", userId);
if (!isRevoked) return false;
return true;
}
Other implementation examples:
https://github.com/jooyoungho/apple-token-revoke-in-firebase
https://github.com/invertase/react-native-apple-authentication/issues/282
You did actually delete the user from Firebase but Apple doesn't know about that. You should delete that information also from Apple. Open the Settings app on your iPhone, then tap on your name at the top. Then press "Password & Security", then "Apple ID logins". All Apple ID logins should be listed there and can be deleted.
so... Apple does not provide this service. But I found a workaround.
My sign in process:
1. Check if user signed in before
// Create an `OAuthCredential` from the credential returned by Apple.
final oauthCredential = OAuthProvider("apple.com").credential(
idToken: appleCredential.identityToken,
rawNonce: rawNonce,
);
// If you can not access the email property in credential,
// means that user already signed in with his appleId in the application once before
bool isAlreadyRegistered = appleCredential.email == null;
Now to the crucial part:
2. sign in user and check if that uid already exists in Firebase
final UserCredential result =
await FirebaseAuth.instance.signInWithCredential(
oauthCredential,
);
isAlreadyRegistered = await BackendService.checkIfUserIdExists(
result.user?.uid ?? '',
);
checkIfUserIdExists is quite simple as well:
static Future<bool> checkIfUserIdExists(String userId) async {
try {
var collectionRef = FirebaseFirestore.instance.collection(
BackendKeys.users,
);
var doc = await collectionRef.doc(userId).get();
return doc.exists;
} on FirebaseException catch (e) {
return false;
}
}

skipped queue in MassTransit with RabbitMQ in dot net core application

I have three projects. One is Dot net core MVC, two are API projects. MVC is calling one API for user details. When user details are asked, I am sending message to queue through MassTransit. I am seeing skipped queue. There's consumer in third project which is API project.
I tried to make another solution for a demo with same configuration. It's running fine.
Below is MVC Razor page code..
public async Task<IActionResult> OnPostAsync(string returnUrl = null)
{
ReturnUrl = returnUrl;
if (ModelState.IsValid)
{
var user = await AuthenticateUser(Input.Email);
if (user == null)
{
ModelState.AddModelError(string.Empty, "Invalid login attempt.");
return Page();
}
#region snippet1
var claims = new List<Claim>
{
new Claim(ClaimTypes.Name, user.Email),
new Claim("FullName", user.FullName),
new Claim(ClaimTypes.Role, "Administrator"),
};
var claimsIdentity = new ClaimsIdentity(
claims, CookieAuthenticationDefaults.AuthenticationScheme);
var authProperties = new AuthenticationProperties
{
ExpiresUtc = DateTimeOffset.UtcNow.AddMinutes(15),
IsPersistent = true,
};
await HttpContext.SignInAsync(
CookieAuthenticationDefaults.AuthenticationScheme,
new ClaimsPrincipal(claimsIdentity),
authProperties);
#endregion
_logger.LogInformation("User {Email} logged in at {Time}.",
user.Email, DateTime.UtcNow);
return LocalRedirect(Url.GetLocalUrl(returnUrl));
}
return Page();
}
private async Task<ApplicationUser> AuthenticateUser(string email)
{
if (!string.IsNullOrEmpty(email))
{
using (var client = new System.Net.Http.HttpClient())
{
var request = new System.Net.Http.HttpRequestMessage();
request.RequestUri = new Uri("http://localhost:52043/api/user?uName=" + email); // ASP.NET 3 (VS 2019 only)
var response = await client.SendAsync(request);
var customer = Newtonsoft.Json.JsonConvert.DeserializeObject<Customers>(response.Content.ReadAsStringAsync().Result);
return new ApplicationUser()
{
Email = email,
FullName = customer.FullName
};
}
}
else
{
return null;
}
}
MVC Startup:
services.AddMassTransit(x =>
{
x.AddBus(provider => Bus.Factory.CreateUsingRabbitMq(cfg =>
{
// configure health checks for this bus instance
cfg.UseHealthCheck(provider);
cfg.Host("rabbitmq://localhost");
}));
});
services.AddMassTransitHostedService();
User API Code - 52043:
[HttpGet]
public async Task<IActionResult> Get(string uName)
{
var customer = _userRepository.GetCustomerByUserName(uName);
Uri uri = new Uri("rabbitmq://localhost/loginqueue");
var endpoint = await _bus.GetSendEndpoint(uri);
await endpoint.Send(new LoginObj() { NoteString = customer.FullName + " has logged in at " + DateTime.Now.ToString() });
return Json(customer);
}
Logging API - Consumer Code:
public class LoginConsumer : IConsumer<LoginObj>
{
private readonly ILogger<object> _logger;
public LoginConsumer(ILogger<object> logger)
{
_logger = logger;
}
public async Task Consume(ConsumeContext<LoginObj> context)
{
var data = context.Message;
_logger.LogInformation(data.ToString());
}
}
Login API Startup:
services.AddMassTransit(x =>
{
x.AddConsumer<LoginConsumer>();
x.AddBus(provider => Bus.Factory.CreateUsingRabbitMq(cfg =>
{
// configure health checks for this bus instance
cfg.UseHealthCheck(provider);
cfg.Host("rabbitmq://localhost");
cfg.ReceiveEndpoint("loginqueue", ep =>
{
ep.PrefetchCount = 16;
ep.UseMessageRetry(r => r.Interval(2, 100));
ep.ConfigureConsumer<LoginConsumer>(provider);
});
}));
});
services.AddMassTransitHostedService();
As per the documentation:
MassTransit uses the full type name, including the namespace, for message contracts. When creating the same message type in two separate projects, the namespaces must match or the message will not be consumed.
Make sure that your message type has the same namespace/type in each project.

Calling Microsoft Graph API using first party app fails with insufficient privileges to complete the operation on "SubscribedSkus API"

Calling microsoft graph API https://graph.microsoft.com/v1.0/subscribedSkus fails with
"code": "Authorization_RequestDenied",
"message": "Insufficient privileges to complete the operation.",
This is happening if we create a new user in the tenant who is non admin. But while calling this with Admin user it works just fine. Even it works for any microsoft user in the tenant.
This is the below code I used to try.
public static async Task TestAadGraph()
{
// using obo token of the user.
var graphToken = await GetTokenAsync(UserId, Token, "https://graph.microsoft.com");
var aadGraphClient = new AadGraphClient(new HttpClient());
var licenseResponse = await aadGraphClient.GetTenantLicenseDetailAsync(graphToken);
foreach (var license in licenseResponse)
{
Console.WriteLine("Sku ID: {0}", license.SkuId);
Console.WriteLine("Sku Part Number: {0}", license.SkuPartNumber);
foreach (var plan in license.ServicePlans)
{
Console.WriteLine("Plan Id: {0}", plan.ServicePlanId);
Console.WriteLine("Plan Name: {0}", plan.ServicePlanName);
}
}
}
public async Task<SubscribedSku[]> GetTenantLicenseDetailAsync(string accessToken)
{
var request = new RequestMessage
{
BearerToken = accessToken,
Endpoint = new Uri("http://graph.microsoft.com/v1.0/subscribedSkus"),
};
var response = await _httpClient.FetchAsync<SubscribedSkusResponse>(request);
return response.Value;
}
public static async Task<T> FetchAsync<T>(this HttpClient httpClient, RequestMessage request, Action<HttpResponseMessage, string> responseCallback) where T : class
{
request.Method = request.Method ?? HttpMethod.Get;
request.MediaType = request.MediaType ?? "application/json";
using (HttpRequestMessage message = new HttpRequestMessage(request.Method,
UrlHelper.AppendParameters(request.Params, request.Endpoint)))
{
if (!string.IsNullOrEmpty(request.BearerToken))
{
message.Headers.Authorization = new AuthenticationHeaderValue("Bearer",
request.BearerToken);
}
if (request.Headers != null)
{
foreach (KeyValuePair<string, string> header in request.Headers)
{
message.Headers.Add(header.Key, header.Value);
}
}
if (!string.IsNullOrEmpty(request.Content))
{
message.Content = new StringContent(request.Content, Encoding.UTF8,
request.MediaType);
}`
using (HttpResponseMessage response = await httpClient.SendAsync(message))
{
string json = await response.Content.ReadAsStringAsync();
if (responseCallback != null)
{
responseCallback?.Invoke(response, json);
}
if (response.IsSuccessStatusCode)
{
if (predictor != null)
{
json = predictor(JToken.Parse(json)).ToString();
}
return JsonConvert.DeserializeObject<T>(json);
}
else
{
throw new WebRequestException(response, json);
}
}
}
}
Firstly, try the same call for the new created user in Microsoft Graph Explorer to see if the same scene exists. If not, it means the there is nothing wrong with the new user.
Then debug your code and copy the graphToken into https://jwt.io/ and see if the Decoded result has one of the required Delegated permissions:Organization.Read.All, Directory.Read.All, Organization.ReadWrite.All, Directory.ReadWrite.All, Directory.AccessAsUser.All. Note that the "upn" should be the username of the new created user.
If the required permissions do not exist, you will need to assign permissions in the Azure AD app. See API permissions.
Perfect. Adding permission to the first party app has actually worked.

ASP.NET MVC Web API : Posting a list of objects

I'm trying to post a list of objects from my winforms application to my asp.net mvc 4 website. I've tested posting one object, and it works, but does not work for the list. It returns a 500 (Internal Server Error). Here is my code:
ASP.NET MVC Web API
public class PostTraceController : ApiController
{
public HttpResponseMessage Post(List<WebTrace> list)
{
try
{
// Some code
return Request.CreateResponse(HttpStatusCode.Created);
}
catch (Exception ex)
{
HttpContext.Current.Trace.Write("exception", ex.Message);
return Request.CreateErrorResponse(HttpStatusCode.ServiceUnavailable, ex);
}
}
public HttpResponseMessage Post(WebTrace item)
{
try
{
// Some code
return Request.CreateResponse(HttpStatusCode.Created);
}
catch (Exception ex)
{
HttpContext.Current.Trace.Write("exception", ex.Message);
return Request.CreateErrorResponse(HttpStatusCode.ServiceUnavailable, ex);
}
}
}
Win forms application
public class BaseSender
{
public BaseSender()
{
Client = new HttpClient
{
BaseAddress = new Uri(#"http://localhost/mywebsite/")
};
Client.DefaultRequestHeaders.Accept.Add(
new MediaTypeWithQualityHeaderValue("application/json"));
}
public string UserCode { get; set; }
protected readonly HttpClient Client;
public HttpResponseMessage PostAsJsonAsync(string requestUri, object value)
{
var response = Client.PostAsJsonAsync(requestUri, value).Result;
response.EnsureSuccessStatusCode();
return response;
}
}
public class WebTraceSender : BaseSender
{
private const string requestUri = "api/posttrace";
public bool Post(List<ArchiveCptTrace> list)
{
try
{
var listWebTrace = new List<WebTrace>();
foreach (var item in list)
{
listWebTrace.Add(new WebTrace
{
DateStart = item.DatePreparation,
DateEnd = item.DateCloture,
UserStart = item.UserPreparation.UserName,
UserEnd = item.UserCloture.UserName,
AmountStart = item.MontantPreparation,
AmountEnd = item.MontantCloture,
TheoricAmountEnd = item.MontantTheorique,
Difference = item.Ecart,
UserCode = UserCode
});
}
var responce = PostAsJsonAsync(requestUri, listWebTrace);
return responce.IsSuccessStatusCode;
}
catch (Exception e)
{
// TODO : Trace the exception
return false;
}
}
}
EDIT :
I've found out the scenario of the error, which is having two methods in my api controller, even thought they have different signature. If I comment one method, the post work fine (item or a list). Any ideas ?
The methods may have different signatures, but Web API can't tell the difference between them without inspecting the body, which it won't do for performance reasons.
You could do two things - either create a new class which just holds a list of WebTrace objects, and put that in a different API controller, or you could map a custom route to one of your existing methods. You could do that with ActionName attribute, however, I would probably take the first approach.

Resources