I need to track database changes in net core 2.0 EF. It all works but I don't know how to get userId inside DbContext. I need to get UserId to assign changes to the user. I cannot simply run _userManager.GetUserId(HttpContext.User).ToString();. Any suggestions? Thanks in advance for your help.
In your ConfigureServices method in Startup.cs add:
services.AddSingleton<IHttpContextAccessor, HttpContextAccessor>();
services.AddEntityFrameworkSqlServer();
The services.AddEntityFrameworkSqlServer() bit adds the service resolver into your context, allowing you to utilize theGetService extension method on your context.
Then, inside your context you can do:
var httpContextAccessor = this.GetService<IHttpContextAccessor>();
The result may be null, particularly if the context is not running within a web application (i.e. during migrations, console apps utilizing your context, etc.). As a result, you should do proper null checking. The following uses C# 6's null-conditional operator:
var userId = httpContextAccessor?.HttpContext.User.FindFirstValue(ClaimTypes.NameIdentifier);
Then, userId will either have the id of the currently logged in user or be null.
Related
Ok, I remember back in regular ASP.NET 4 (before .NET Core - 2015 ish) it was not this convoluted to add a user to a role. But now I found it to be very difficult.
Using Sqlite database, and all the scaffolding and account creation works great. Even imported my contact subscriber list and it seamlessly created the CRUD - awesome.
Now I only need to restrict this page to Admins only, which I did this and works -- no access. I am on the step to add an Admin role and add a user to it.
After reading and trying code from many sites I find myself here for some direction.
Also I see a role and role claim which is confusing..
Ref: How to create roles in ASP.NET Core and assign them to users?
This may work if I knew where to put this for it to find the proper references.
private readonly UserManager<ApplicationUser> _userManager;
private readonly RoleManager<ApplicationRole> _rolesManager;
Not sure where the ApplicationUser and ApplicationRole is coming from.
Assuming you have correctly configured Identity in your application with something similar to the following
builder.Services
.AddIdentity<IdentityUser, IdentityRole>(options =>
options.SignIn.RequireConfirmedAccount = true)
.AddEntityFrameworkStores<WebApplication2Context>();
(in your case, IdentityUser is probably going to map to your AspNetUser class and IdentityRole would be your AspNetRole class, though I'm not sure where you got those classes or whether they properly inherit IdentityUser<T> and IdentityRole<T> - if they don't, you've gone down a dark and scary road...)
Once that's done (and you've applied all the EF Core migrations, etc. to get your database correctly built), you can do something like this to add a role and add a user to it
#page
#model IndexModel
#using Microsoft.AspNetCore.Identity
#inject UserManager<IdentityUser> _userManager
#inject RoleManager<IdentityRole> _roleManager
#{
ViewData["Title"] = "Home page";
await _roleManager.CreateAsync(new IdentityRole("Admin"));
await _userManager.CreateAsync(new IdentityUser("foobar") { Email = "foo#bar.com" });
var newUser = await _userManager.FindByNameAsync("foobar");
await _userManager.AddToRoleAsync(newUser, "Admin");
}
If you're not sure your existing AspNetXxx classes are correct or correctly map to the related Identity classes, I'd suggest you start over and use the default implementations as much as possible. You can read about Identity Model Customization in ASP.NET Core to learn about the model and how all the different tables work together.
I think you need to "link" your "MyPolicyName" to your MVC.
https://learn.microsoft.com/en-us/aspnet/core/security/authorization/policies?view=aspnetcore-6.0#apply-policies-to-razor-pages
using Microsoft.AspNetCore.Authorization;
using Microsoft.AspNetCore.Mvc.RazorPages;
namespace AuthorizationPoliciesSample.Pages;
[Authorize(Policy = "AtLeast21")]
public class AtLeast21Model : PageModel { }
...
Because of this "string-matching-magic" I would create a single-source-of-truth class.
public static class MyPolicyNames
{
public static string AtLeast21PolcyName = "AtLeast21PolicyName";
}
and refer to this const in both places. And eliminate future "where are the magic-strings" scavenger hunts.
=============
So you are using a Policy, but your "policy rule" is to then check a Role.
Should you be just using Role?
https://learn.microsoft.com/en-us/aspnet/core/security/authorization/roles?view=aspnetcore-6.0
I guess M$ says its "ok"
https://learn.microsoft.com/en-us/aspnet/core/security/authorization/roles?view=aspnetcore-6.0#policy-based-role-checks
How I can manage to login with email address,not with the standart (username + password).I enter the website with my Users in my DataBase , but is there a way to change that to be with email address instead of that user name , because when I use Gii , I got a lot of errors , even I try to fix those errors
First, try to locate your SiteController or any other Controller you use for the index route. It should have an action function that corresponds to the login route; it is usually with signature public function actionLogin().
You should see the initialized model (usually, the LoginForm model). The model should have a function for login logic which is checked to determine user authenticity. You should find that this function invokes another login function which requires the User object as first argument/parameter. The function is usually the $this->getUser() function.
Looking into this will point to you a call to the actual data model that fetches user by whatever criteria/property you specify; this can be email or anything else that might not even need be unique but generally, you want to use a unique data property like username and email. This function relies on the User data model. It, by Gii default, calls the function User::findByUsername(search_property)
Yii2 provides a default User model that implements the Identity interface; that's where you want to make the adjustment you need. It should have the required static function findByUsername() or something similar. You would find that Yii2 default searches within static data to find user, you should link that to you (User) data model which I assume you generated using Gii.
My Gii sequence usually looks like such:
List item
Generate the yii2-basic/yii2-advanced using composer
Create Database (I have a user table in there) and set proper db credentials in config/db.php
Rename the default model/User.php to model/OldUser.php
Create Data Models using Gii
Make the newly generated User Model implement IdentityInterface to allow Yii2 freely-given session management by adding implements yii\web\IdentityInterface to the class declaration.
Implement all the required methods of the IdentityInterface. You can check in `model/OldUser.php' for guidance.
Create static functions to findUserByEmail($email) or findUserByUsername($username)
Mine usually look like this
I hope this helps.
I made it just change everywhere where must be email,instead of username,because of Yii default username loggin , thank u for the advices
I'm upgrading a custom solution where I can dynamically register and unregister Web Api controllers to use the new attribute routing mechanism. However, it seems to recent update to RTM break my solution.
My solution exposes a couple of Web Api controllers for administration purposes. These are registered using the new HttpConfigurationExtensions.MapHttpAttributeRoutes method call.
The solution also allows Web Api controllers to be hosted in third-party assemblies and registered dynamically. At this stage, calling HttpConfigurationExtensions.MapHttAttributeRoutes a second time once the third-party controller is loaded would raise an exception. Therefore, my solution uses reflection to inspect the RoutePrefix and Route attributes and register corresponding routes on the HttpConfiguration object.
Unfortunately, calling the Web Api results in the following error:
"No HTTP resource was found that matches the request URI".
Here is a simple controller that I want to use:
[RoutePrefix("api/ze")]
public sealed class ZeController : ApiController
{
[HttpGet]
[Route("one")]
public string GetOne()
{
return "One";
}
[HttpGet]
[Route("two")]
public string GetTwo()
{
return "Two";
}
[HttpPost]
[Route("one")]
public string SetOne(string value)
{
return String.Empty;
}
}
Here is the first solution I tried:
configuration.Routes.MapHttpRoute("ZeApi", "api/ze/{action}");
Here is the second solution I tried:
var type = typeof(ZeController);
var routeMembers = type.GetMethods().Where(m => m.IsPublic);
foreach (MethodInfo method in routeMembers)
{
var routeAttribute = method.GetCustomAttributes(false).OfType<RouteAttribute>().FirstOrDefault();
if (routeAttribute != null)
{
string controllerName = type.Name.Substring(0, type.Name.LastIndexOf("Controller"));
string routeTemplate = string.Join("/", "api/Ze", routeAttribute.Template);
configuration.Routes.MapHttpRoute(method.Name, routeTemplate);
}
}
I also have tried a third solution, whereby I create custom classes that implement IHttpRoute and trying to register them with the configuration to no avail.
Is it possible to use legacy-style route mapping based upon the information contained in the new routing attributes ?
Update
I have installed my controller in a Web Application in order to troubleshoot the routing selection process with the Web Api Route Debugger. Here is the result of the screenshot:
As you can see, the correct action seems to be selected, but I still get a 404 error.
Update2
After further analysis, and per Kiran Challa's comment below, it seems that the design of Web Api prevents mixing attribute routing and conventional routing, and that what I want to do is not possible using this approach.
I have created a custom attribute [RouteEx] that serves the same purpose of the Web Api [Route] attribute, and now my code works perfectly.
I guess, since this is not possible using the conventional attribute routing, none of the answers on this question could legitimately be consisered valid. So I'm not nominating an answer just yet.
You shouldn't be required to use reflection and inspect the attribute-routing based attributes yourself. Attribute routing uses existing Web API features to get list of controllers to scan through.
Question: Before the switch to attribute routing, how were you loading these assemblies having the
controllers?
If you were doing this by IAssembliesResolver service, then this solution should work even with attribute routing and you should not be needing to do anything extra.
Regarding your Update: are you calling MapHttpAttributeRoutes?
I've an entity with an ID of
public string ID {get;set;}
activities/1
(which comes from RavenDB).
I'm registering the following routes in my ServiceStack AppHost
Routes
.Add<Activity>("/activities")
.Add<Activity("/activities/{id}");
I'm using a backbone app to POST and PUT to my REST Service.
What happens out-of-the-box:
id property is serialized into the json as "activities/1"
id property is encoded into route as "activities%2F1"
ServiceStack gives precedence to the URL based id property, so my string gets the encoded value which is no use to RavenDb directly.
The options I'm aware of:
Change backbone to post to "/activities" and let the JSON Serialiser kick in
Change RavenDb ID generation to use hyphens rather than slashes
Make my Id property parse for the encoded %2F on set and convert to a slash
Both have disadvantages in that I either lose RESTfulness in my API, which is undesirable, or I don't follow RavenDb conventions, which are usually sensible out-of-the-fox. Also, I've a personal preference for having slashes.
So I'm wondering if there are any other options in servicestack that I could use to sort this issue that involve less compromise? Either Serialiser customisation or wildcard routing are in my head....
I have the same problem with ASP.Net WebAPI, so I don't think this is so much a ServiceStack issue, but just a general concern with dealing with Raven style id's on a REST URL.
For example, let's say I query GET: /api/users and return a result like:
[{
Id:"users/1",
Name:"John"
},
{
Id:"users/2",
Name:"Mary"
}]
Now I want to get a specific user. If I follow pure REST approach, the Id would be gathered from this document, and then I would pass it in the id part of the url. The problem here is that this ends up looking like GET: /api/users/users/1 which is not just confusing, but the slash gets in the way of how WebAPI (and ServiceStack) route url parameters to action methods.
The compromise I made was to treat the id as an integer from the URL's perspective only. So the client calls GET: /api/users/1, and I define my method as public User Get(int id).
The cool part is that Raven's session.Load(id) has overloads that take either the full string form, or the integer form, so you don't have to translate most of the time.
If you DO find yourself needing to translate the id, you can use this extension method:
public static string GetStringIdFor<T>(this IDocumentSession session, int id)
{
var c = session.Advanced.DocumentStore.Conventions;
return c.FindFullDocumentKeyFromNonStringIdentifier(id, typeof (T), false);
}
Calling it is simple as session.GetStringIdFor<User>(id). I usually only have to translate manually if I'm doing something with the id other than immediately loading a document.
I understand that by translating the ids like this, that I'm breaking some REST purist conventions, but I think this is reasonable given the circumstances. I'd be interested in any alternative approaches anyone comes up with.
I had this problem when trying out Durandal JS with RavenDB.
My workaround was to change the URL very slightly to get it to work. So in your example:
GET /api/users/users/1
Became
GET /api/users/?id=users/1
From jQuery, this becomes:
var vm = {};
vm.users = [];
$.get("/api/users/?" + $.param( { id: "users/1" })
.done(function(data) {
vm.users = data;
});
In our SharePoint application we have used the UnitOfWork + Repository patterns together with Entity Framework. To avoid the usage of the passthrough authentication we have developed a piece of code that impersonate a single user before creating the ObjectContext instance in a similar way that is described in "Impersonating user with Entity Framework" on this site.
The only difference between our code and the referred question is that, to do the impersonation, we are using RunWithElevatedPrivileges to impersonate the Application Pool identity as in the following sample.
SPSecurity.RunWithElevatedPrivileges(delegate() {
using (SPSite site = new SPSite(url)) {
_context = new MyDataContext(ConfigSingleton.GetInstance().ConnectionString);
}
});
We have done this way because we expected that creating the ObjectContext after impersonation and, due to the fact that Repositories are receiving the impersonated ObjectContext would solve our requirement.
Unfortunately it's not so easy. In fact we experienced that, even if the ObjectContext is created before and under impersonation circumstances, the real connection is made just before executing the query, and so does not use impersonation, which break our requirement.
I have checked the ObjectContext class to see if there was any event through which we can inject the impersonation but unfortunately found nothing.
Any help?
We had a simillar problem when we used LinqToSharePoint. The DataContext is created from the HttpContext.Current and did not consider the RunWithElevatedPrivileges method. We did a nasty workaround that we backed up the original HttpContext, created a new dummy HttpContext in the RunWithElevatedPrivileges method and the problem went away. Obviously we set the context to the original afterwards.
Edit:
You can use the method below to create new dummy HttpContext.Call this method as first in your RunWithElevatedPrivileges. In the normal context just backup your currenct context with var backupContext = HttpContext.Current and after everything is done just set the context back.
private void SetNewContextWeb(SPWeb oWeb)
{
HttpRequest httpRequest = new HttpRequest(string.Empty, oWeb.Url, string.Empty);
HttpContext.Current = new HttpContext(httpRequest, new HttpResponse(new System.IO.StringWriter()));
SPControl.SetContextWeb(HttpContext.Current, oWeb);
}