I currently code a Discord bot in Java with Discord4j. Now, I want to get the mentioned user IDs (already did that) and convert them into Member object, but I don't know how to do it.
That's my code currently:
public static void run(MessageCreateEvent event) {
if(event.getMessage().getUserMentionIds().toString() == "[]") {
Utils.sendMessageToChannel("ERROR: No User got mentioned.", event);
}
Snowflake userMentions = Snowflake.of(event.getMessage().getUserMentionIds().toString());
Member member = new Member(userMentions);
}
Discord4J does not allow the use of constructors on its entities and most objects. This is because all of the entities are built up from data from the cache and/or a rest request to discord.
To get a Member from a user ID you would do the following:
GatewayDiscordClient#getMemberById(guildId, userId).block();
If you just want the user, as Member requires the person be in the guild:
GatwayDiscordClient#getUserById(userId).block();
You can also do this reactively by (flat)mapping the Mono(s).
Related
I've created a Blazor Server App with the option to scaffold an identity system. This created an Entity Framework IdentityDbContext with a number of tables to manage user logins and settings. I decided to keep my own DbContext separate from this so that I could replace either of the contexts later, if necessary.
What I would like to do is have a User entity in my own custom dbcontext, and in it store a reference to the user id of the scaffolded IdentityDbContext entity. I would also like to ensure that I don't have to query the db for the custom entity every time the user opens a new page.
I've been looking around StackOverflow trying to find good suggestions of how to approach this, but I'm still not sure how to start. So I have a few questions:
Is my approach a sensible one?
How do I find a permanent id number or string to couple with on the UserIdentity?
Should I store my custom user entity in some sort of context so I don't have to query it all the time? If so, how?
All help is greatly appreciated!
It looks like your requirement is to store custom information about the current user above and beyond what is stored in Identity about the current user.
For simpler use cases you can create your own User class derived from IdentityUser and add additional properties on there and let Identity take care of all persistence and retrieval.
For more complex use cases you may follow the approach you have taken, whereby you create your own tables to store user related information.
It seems that you have taken the second approach.
Is my approach a sensible one?
I think so. Burying lots of business-specific context about the user in the Identity tables would tightly bind you to the Identity implementation.
How do I find a permanent id number or string to couple with on the
UserIdentity?
IdentityUser user = await UserManager<IdentityUser>.FindByNameAsync(username);
string uniqueId = user.Id;
// or, if the user is signed in ...
string uniqueId = UserManager<IdentityUser>.GetUserId(HttpContext.User);
Should I store my custom user entity in some sort of context so I
don't have to query it all the time? If so, how?
Let's say you have a class structure from your own DbContext that stores custom information about the user, then you can retrieve that when the user signs in, serialize it, and put it in a claim on the ClaimsPrincipal. This will then be available to you with every request without going back to the database. You can deserialize it from the Claims collection as needed and use it as required.
How to ...
Create a CustomUserClaimsPrincipalFactory (this will add custom claims when the user is authenticated by retrieving data from ICustomUserInfoService and storing in claims):
public class CustomUserClaimsPrincipalFactory
: UserClaimsPrincipalFactory<ApplicationUser, IdentityRole>
{
private readonly ICustomUserInfoService _customUserInfoService;
public CustomUserClaimsPrincipalFactory(
UserManager<ApplicationUser> userManager,
RoleManager<IdentityRole> roleManager,
IOptions<IdentityOptions> optionsAccessor,
ICustomUserInfoService customUserInfoService)
: base(userManager, roleManager, optionsAccessor)
{
_customUserInfoService= customUserInfoService;
}
protected override async Task<ClaimsIdentity> GenerateClaimsAsync(
ApplicationUser user)
{
var identity = await base.GenerateClaimsAsync(user);
MyCustomUserInfo customUserInfo =
await _customUserInfoService.GetInfoAsync();
// NOTE:
// ... to add more claims, the claim type need to be registered
// ... in StartUp.cs : ConfigureServices
// e.g
//services.AddIdentityServer()
// .AddApiAuthorization<ApplicationUser, ApplicationDbContext>(options =>
// {
// options.IdentityResources["openid"].UserClaims.Add("role");
// options.ApiResources.Single().UserClaims.Add("role");
// options.IdentityResources["openid"].UserClaims.Add("my-custom-info");
// options.ApiResources.Single().UserClaims.Add("my-custom-info");
// });
List<Claim> claims = new List<Claim>
{
// Add serialized custom user info to claims
new Claim("my-custom-info", JsonSerializer.Serialize(customUserInfo))
};
identity.AddClaims(claims.ToArray());
return identity;
}
}
Register your CustomUserInfoService in Startup.cs (your own service to get your custom user info from the database):
services.AddScoped<ICustomUserInfoService>(_ => new CustomUserInfoService());
Register Identity Options (with your CustomUserClaimsPrincipalFactory and authorisation in Startup.cs. NOTE: addition of "my-custom-info" as a registered userclaim type. Without this your code in CustomUserInfoService will fail to add the claim type "my-custom-info":
services.AddDefaultIdentity<IdentityUser>(options =>
{
options.SignIn.RequireConfirmedAccount = false;
options.User.RequireUniqueEmail = true;
})
.AddRoles<IdentityRole>()
.AddEntityFrameworkStores<ApplicationDbContext>()
.AddClaimsPrincipalFactory<CustomUserClaimsPrincipalFactory>();
services.AddIdentityServer()
.AddApiAuthorization<ApplicationUser, ApplicationDbContext>(options =>
{
options.IdentityResources["openid"].UserClaims.Add("role");
options.ApiResources.Single().UserClaims.Add("role");
options.IdentityResources["openid"].UserClaims.Add("my-custom-info");
options.ApiResources.Single().UserClaims.Add("my-custom-info");
});
You can then retrieve your custom user info from claims, without returning to database, by using:
MyCustomUserInfo customUserInfo =
JsonSerializer.Deserialize<MyCustomUserInfo>(
HttpContext.User.Claims
.SingleOrDefault(c => c.Type == "my-custom-info").Value);
A question about rest url mappings.. I would like to use a different id in the rest calls then the database id.
Lets say i have an url mapping like this:
"/books"(resources:"book")
Then the show action would be..
def show(Book book) {
if(book == null) {
render status:404
}
else {
return [book: book]
}
}
This would load the book belonging the the id that was passed in via the rest call like: http://url/books/1
If i want to use a different property of the class Book how would i get the automatic databind working? Assume that my book also has a uuid.
How do i get http://url/books/4860ca28-9a76-480f-b898-fffedda1888f to work in the same way as the database id?
I have implemented a CurrentUserPropertyBinder (see below) for a web application using FubuMVC.
public class CurrentUserPropertyBinder : IPropertyBinder
{
private readonly Database _database;
private readonly ISecurityContext _security;
public CurrentUserPropertyBinder(Database database, ISecurityContext security)
{
_database = database;
_security = security;
}
public bool Matches(PropertyInfo property)
{
return property.PropertyType == typeof(User)
&& property.Name == "CurrentUser";
}
public void Bind(PropertyInfo property, IBindingContext context)
{
var currentUser = //check database passing the username to get further user details using _security.CurrentIdentity.Name
property.SetValue(context.Object, currentUser, null);
}
}
When I login to my site, this works fine. The CurrentUserPropertyBinder has all the information it requires to perform the task (i.e. _security.CurrentIdentity.Name has the correct User details in it)
When I try and import a file using fineUploader (http://fineuploader.com/) which opens the standard fileDialog the _security.CurrentIdentity.Name is empty.
It doesn't seem to remember who the user was, I have no idea why. It works for all my other routes but then I import a file it will not remember the user.
Please help! Thanks in Advance
NOTE: We are using FubuMVC.Authentication to authenticate the users
I'm guessing your action for this is excluded from authentication; perhaps it's an AJAX-only endpoint/action. Without seeing what that action looks like, I think you can get away with a simple fix for this, if you've updated FubuMVC.Authentication in the past 3 months or so.
You need to enable pass-through authentication for this action. Out of the box, FubuMVC.Auth only wires up the IPrincipal for actions that require authentication. If you want access to that information from other actions, you have to enable the pass-through filter. Here are some quick ways to do that.
Adorn your endpoint/controller class, this specific action method, or the input model for this action with the [PassThroughAuthentication] attribute to opt-in to pass-through auth.
[PassThroughAuthentication]
public AjaxContinuation post_upload_file(UploadInputModel input) { ... }
or
[PassThroughAuthentication]
public class UploadInputModel { ... }
Alter the AuthenticationSettings to match the action call for pass-through in your FubuRegistry during bootstrap.
...
AlterSettings<AuthenticationSettings>(x => {
// Persistent cookie lasts 3 days ("remember me").
x.ExpireInMinutes = 4320;
// Many ways to filter here.
x.PassThroughChains.InputTypeIs<UploadInputModel>();
});
Check /_fubu/endpoints to ensure that the chain with your action call has the pass-through or authentication filter applied.
I have a big problem since some days and I’m a very beginner in the Entity Framework.
I have 2 entities : Group and News. A news is visible by one or many groups. I use two repositories (newsRepository and groupsRepository).
This is my Create method for the news :
public ActionResult Create()
{
return View(new CreateNewsViewModel(new News()));
}
[HttpPost]
public ActionResult Create(CreateNewsViewModel model)
{
model.news.CategoryId = Int32.Parse(Request.Form["news.CategoryId"]);
if (ModelState.IsValid)
{
News news = new News();
DateTime date = DateTime.Now;
//AuthorId a recuperer
news.AuthorId = 1;
news.Title = IntranetTools.UppercaseFirst(model.news.Title.Trim());
news.Content = model.news.Content;
news.IsVisible = Request.Form["news.IsVisible"].Contains("true");
news.CreateDate = date;
news.PublicationDate = date;
news.LastChangedDate = date;
news.CategoryId = model.news.CategoryId;
// Collection des groupes concernés
foreach (var c in model.allGroups)
{
if (Request.Form["" + c.GroupId].Contains("true"))
{
News.Groups.Add(c);
}
}
_newsRepository.AddToNewsSet(news);
_newsRepository.SaveChanges();
return Redirect("/NewsAdmin/Index/");
}
return View(model);
}
I say that all my groups are already created. I just want to insert the groups (chosen by the user via checkboxes). In my “CreateNewsViewModel”, I create a list of groups that contains all existing groups in my DB. I get the list in my view, via a “foreach” loop and create a checkbox for each group.
I reuse the same list in my controller to compare if checkboxes have been checked.
For each “true” value, I add groups in the groups collection of my news (just created).
With this, I get this Error Message :
“An entity object cannot be referenced by multiple instances of IEntityChangeTracker.” (at line _newsRepository.AddToNewsSet(news);)
I try some solutions but I still don’t understand how I can solve this problem.
Thanks for all
Edit
Actually, if I use explicitly two contexts and detach/attach my objects to other context it's fine and I have no erros.
ObjectContext context = _newsRepository.Context;
ObjectContext context2 = _groupsRepository.Context;
foreach (var c in groups)
{
if (Request.Form["" + c.GroupId].Contains("true"))
{
context2.Detach(c);
context.Attach(c);
news.Groups.Add(c);
}
}
I would like to use the Ladislav Mrnka's solution and use the dependency injection (I use Ninject framework) to give the same ObjectContext to my repositories (in single request processing).
I understand the concept but I don't know how to code it.
The error message says that News object or any of related Group objects is attached to different ObjectContext instance. How is your repository implemented and how did you get model.allGroups? If you loaded allGroups from GroupsRepository which has its own ObjectContext instance then it is probably source of the problem. The solution would be:
(Preferred) Share single ObjectContext for all repositories in single request processing
Detach objects when you load them from database (ObjectContext has Detach method)
Close ObjectContext when you load objects from database
This is more like a conceptual question. When to use Model Binding (in ASP.NET MVC Framework) and when to inject objects using IoC (lets say Autofac here) ?
One specific scenario is like lets say, I have the following action method
public ActionResult EditProfile(string UserId)
{
// get user object from repository using the the UserId
// edit profile
// save changes
// return feedback
}
In the above scenario, is it possible to inject a user object to action method such that it automatically gets the user object using the UserId ? The resulting signature being:
public ActionResult EditProfile(UserProfile userObj) //userObj injected *somehow* to automatically retreive the object from repo using UserId ?
Sorry if it all doesn't makes sense. It`s my first time using IoC.
EDIT:
This is the way to do it > http://buildstarted.com/2010/09/12/custom-model-binders-in-mvc-3-with-imodelbinder/
You can do what you need using a custom action filter. By overriding OnActionExecuting, we have access to the route data, and the action parameters of the action that will be executed. Given:
public class BindUserProfileAttribute : ActionFilterAttribute
{
public override OnActionExecuting(FilterContext filterContext)
{
string id = (string)filterContext.RouteData.Values["UserId"];
var model = new UserProfile { Id = id };
filtextContext.ActionParameters["userObj"] = model;
}
}
This attribute allows us to create the parameters that will be passed into the action, so we can load the user object at this point.
[BindUserProfile]
public ActionResult EditProfile(UserProfile userObj)
{
}
You'll probably need to get specific with your routes:
routes.MapRoute(
"EditProfile",
"Account/EditProfile/{UserId}",
new { controller = "Account", action = "EditProfile" });
In MVC3 we get access to the new IDepedencyResolver interface, which allows us to perform IoC/SL using whatever IoC container or service locator we want, so we can push a service like a IUserProfileFactory into your filter, to then be able to create your UserProfile instance.
Hope that helps?
Model binding is used for your data. Dependency injection is used for your business logic.