I have collection of documents Users
User
{
"Status": "ACTIVE",
"Login": {
"UserName": "login",
"Password": null,
"CreationDate": "2011-12-07T11:30:24.4062500Z",
"Roles": [
{
"Id": "roles/WebUser",
"Name": "WebUser"
},
{
"Id": "roles/Admin",
"Name": "Admin"
}
]
},
}
How can i make a query to get list of users with role name "WebUser" except users with role name "Admin" (Contains role "WebUser" but not contains role "Admin")
Using LINQ or lucene
You need to create an index for that, something like:
from user in docs.Users
select new { Roles = user.Logins.Roles.Select(x=>x.Name) }
And then you can query is using:
Roles:WebMaster AND -Roles:Admin
Is this what you want?
var users = documentSession.Query<User>()
.Where(x => x.Login.Roles.Any(y => y.Name == "WebUser"))
.Where(x => x.Login.Roles.Any(y => y.Name != "Admin"))
.ToList();
sample unit test....
using System;
using System.Collections.Generic;
using System.Linq;
using NUnit.Framework;
using Raven.Client;
using Raven.Client.Embedded;
using Raven.Client.Linq;
namespace Foo.Tests.
{
public class UserTests
{
[Test]
// ReSharper disable InconsistentNaming
public void GivenSomeUsersWithWebUserAndAdminRoles_Query_ReturnsSomeUsers()
// ReSharper restore InconsistentNaming
{
IDocumentStore documentStore;
using (documentStore = new EmbeddableDocumentStore {RunInMemory = true})
{
// Arrange.
documentStore.Initialize();
// Create and store Fake Data.
using (IDocumentSession documentSession = documentStore.OpenSession())
{
IEnumerable<User> users = CreateFakeUsers(documentSession);
foreach (var user in users)
{
documentSession.Store(user);
}
documentSession.SaveChanges();
}
using (IDocumentSession documentSession = documentStore.OpenSession())
{
// Act.
var users = documentSession.Query<User>()
.Where(x => x.Login.Roles.Any(y => y.Name == "WebUser"))
.Where(x => x.Login.Roles.Any(y => y.Name != "Admin"))
.ToList();
// Assert.
Assert.IsNotNull(users);
Assert.AreEqual(2, users.Count);
}
}
}
private IEnumerable<User> CreateFakeUsers(IDocumentSession documentSession)
{
return new List<User>
{
new User
{
Status = "ACTIVE",
Login = new Login
{
UserName = "loging",
Password = null,
CreationDate = DateTime.UtcNow,
Roles = new List<Role>
{
new Role
{
Id = "roles/WebUser",
Name = "WebUser"
},
new Role
{
Id = "roles/Admin",
Name = "Admin"
}
}
}
},
new User
{
Status = "ACTIVE",
Login = new Login
{
UserName = "User 2",
Password = null,
CreationDate = DateTime.UtcNow,
Roles = new List<Role>
{
new Role
{
Id = "roles/WebUser",
Name = "WebUser"
}
}
}
},
new User
{
Status = "INACTIVE",
Login = new Login
{
UserName = "User 3",
Password = null,
CreationDate = DateTime.UtcNow,
Roles = new List<Role>
{
new Role
{
Id = "roles/Admin",
Name = "Admin"
}
}
}
}
};
}
#region Nested type: Login
private class Login
{
public string UserName { get; set; }
public string Password { get; set; }
public DateTime CreationDate { get; set; }
public ICollection<Role> Roles { get; set; }
}
#endregion
#region Nested type: Role
private class Role
{
public string Id { get; set; }
public string Name { get; set; }
}
#endregion
#region Nested type: User
private class User
{
public string Id { get; set; }
public string Status { get; set; }
public Login Login { get; set; }
}
#endregion
}
}
Related
In a ASP.NET core 5 application, I use GraphQL with GraphQL.SystemTextJson.
When I attempt to return a result, I get s System.NotSupportedException saying "Serialization and deserialization of 'System.Type' instances are not supported and should be avoided since they can lead to security issues.".
I suspect something to be missing in the configuration of DocumentWriter.
It is configured like this in ConfigureServices:
public void ConfigureServices(IServiceCollection services)
{
services.AddControllers();
...
services.AddScoped<IDocumentWriter, DocumentWriter>();
Any suggestion?
Update:
for completeness, as asked by #AndrewSilver, I report the whole code (adapted from https://www.red-gate.com/simple-talk/dotnet/net-development/building-and-consuming-graphql-api-in-asp-net-core-3-1/ and ported to .net core 5.0).
public class Startup
{
public Startup(IConfiguration configuration)
{
Configuration = configuration;
}
public IConfiguration Configuration { get; }
// This method gets called by the runtime. Use this method to add services to the container.
public void ConfigureServices(IServiceCollection services)
{
services.AddControllers();
services.AddSwaggerGen(c =>
{
c.SwaggerDoc("v1", new OpenApiInfo { Title = "GraphQlExperiments", Version = "v1" });
});
services.AddScoped<IDocumentExecuter, DocumentExecuter>();
services.AddScoped<IDocumentWriter, DocumentWriter>();
services.AddScoped<AuthorService>();
services.AddScoped<AuthorRepository>();
services.AddScoped<AuthorQuery>();
services.AddScoped<AuthorType>();
services.AddScoped<BlogPostType>();
services.AddScoped<ISchema, GraphQLDemoSchema>();
services.AddControllers();
}
// This method gets called by the runtime. Use this method to configure the HTTP request pipeline.
public void Configure(IApplicationBuilder app, IWebHostEnvironment env)
{
if (env.IsDevelopment())
{
app.UseDeveloperExceptionPage();
app.UseSwagger();
app.UseSwaggerUI(c => c.SwaggerEndpoint("/swagger/v1/swagger.json", "GraphQlExperiments v1"));
}
// See: https://github.com/JosephWoodward/graphiql-dotnet
app.UseGraphiQl("/graphiql", "/api/v1/graphql");
app.UseHttpsRedirection();
app.UseRouting();
app.UseAuthorization();
app.UseEndpoints(endpoints =>
{
endpoints.MapControllers();
});
}
}
public class Author
{
public int Id { get; set; }
public string FirstName { get; set; }
public string LastName { get; set; }
}
public class BlogPost
{
public int Id { get; set; }
public string Title { get; set; }
public string Content { get; set; }
public Author Author { get; set; }
}
public class AuthorType : ObjectGraphType<Author>
{
public AuthorType()
{
Name = "Author";
Field(_ => _.Id).Description("Author's Id.");
Field(_ => _.FirstName).Description("First name of the author");
Field(_ => _.LastName).Description("Last name of the author");
}
}
public class BlogPostType : ObjectGraphType<BlogPost>
{
public BlogPostType()
{
Name = "BlogPost";
Field(_ => _.Id, type:
typeof(IdGraphType)).Description("The Id of the Blog post.");
Field(_ => _.Title).Description("The title of the blog post.");
Field(_ => _.Content).Description("The content of the blog post.");
}
}
public class AuthorQuery : ObjectGraphType
{
public AuthorQuery(AuthorService authorService)
{
int id = 0;
Field<ListGraphType<AuthorType>>(
name: "authors",
resolve: context =>
{
return authorService.GetAllAuthors();
});
Field<AuthorType>(
name: "author",
arguments: new QueryArguments(new QueryArgument<IntGraphType> { Name = "id" }),
resolve: context =>
{
id = context.GetArgument<int>("id");
return authorService.GetAuthorById(id);
}
);
Field<ListGraphType<BlogPostType>>(
name: "blogs",
arguments: new QueryArguments(new QueryArgument<IntGraphType> { Name = "id" }),
resolve: context =>
{
return authorService.GetPostsByAuthor(id);
}
);
}
}
public class GraphQLQueryDTO
{
public string OperationName { get; set; }
public string NamedQuery { get; set; }
public string Query { get; set; }
public string Variables { get; set; }
}
public class GraphQLDemoSchema : Schema, ISchema
{
public GraphQLDemoSchema(IServiceProvider resolver) : base(resolver)
{
Query = resolver.GetService<AuthorQuery>();
}
}
public class AuthorService
{
private readonly AuthorRepository _authorRepository;
public AuthorService(AuthorRepository
authorRepository)
{
_authorRepository = authorRepository;
}
public List<Author> GetAllAuthors()
{
return _authorRepository.GetAllAuthors();
}
public Author GetAuthorById(int id)
{
return _authorRepository.GetAuthorById(id);
}
public List<BlogPost> GetPostsByAuthor(int id)
{
return _authorRepository.GetPostsByAuthor(id);
}
}
public class AuthorRepository
{
private readonly List<Author> authors = new List<Author>();
private readonly List<BlogPost> posts = new List<BlogPost>();
public AuthorRepository()
{
Author author1 = new Author
{
Id = 1,
FirstName = "Joydip",
LastName = "Kanjilal"
};
Author author2 = new Author
{
Id = 2,
FirstName = "Steve",
LastName = "Smith"
};
BlogPost csharp = new BlogPost
{
Id = 1,
Title = "Mastering C#",
Content = "This is a series of articles on C#.",
Author = author1
};
BlogPost java = new BlogPost
{
Id = 2,
Title = "Mastering Java",
Content = "This is a series of articles on Java",
Author = author1
};
posts.Add(csharp);
posts.Add(java);
authors.Add(author1);
authors.Add(author2);
}
public List<Author> GetAllAuthors()
{
return this.authors;
}
public Author GetAuthorById(int id)
{
return authors.Where(author => author.Id == id).FirstOrDefault<Author>();
}
public List<BlogPost> GetPostsByAuthor(int id)
{
return posts.Where(post => post.Author.Id == id).ToList<BlogPost>();
}
}
[Route("/api/v1/graphql")]
public class GraphQLController : Controller
{
private readonly ISchema _schema;
private readonly IDocumentExecuter _executer;
public GraphQLController(
ISchema schema,
IDocumentExecuter executer
)
{
_schema = schema;
_executer = executer;
}
[HttpPost]
public async Task<IActionResult> Post([FromBody] GraphQLQueryDTO query)
{
var result = await _executer.ExecuteAsync(_ =>
{
_.Schema = _schema;
_.Query = query.Query;
_.Inputs = query.Variables?.ToInputs();
});
if (result.Errors?.Count > 0)
{
return BadRequest();
}
return Ok(result.Data);
}
}
And this is a sample request that triggers the error:
query {
author (id: 1){
id
firstName
lastName
}
blogs
{
id
title
content
}
}
I solved creating a custom JsonConverter:
public class CustomJsonConverterForType : JsonConverter<Type>
{
public override Type Read(
ref Utf8JsonReader reader,
Type typeToConvert,
JsonSerializerOptions options
)
{
// Caution: Deserialization of type instances like this
// is not recommended and should be avoided
// since it can lead to potential security issues.
// If you really want this supported (for instance if the JSON input is trusted):
// string assemblyQualifiedName = reader.GetString();
// return Type.GetType(assemblyQualifiedName);
throw new NotSupportedException();
}
public override void Write(
Utf8JsonWriter writer,
Type value,
JsonSerializerOptions options
)
{
string assemblyQualifiedName = value.AssemblyQualifiedName;
// Use this with caution, since you are disclosing type information.
writer.WriteStringValue(assemblyQualifiedName);
}
}
Then, in configureServices:
services.AddControllers()
.AddJsonOptions(options =>
{
options.JsonSerializerOptions.WriteIndented = true;
options.JsonSerializerOptions.Converters.Add(new CustomJsonConverterForType());
});
Instead of using System.Text.Json.JsonSearializer Use NewtonSoft.JsonConvert.SearializeObject
I fixed that problem by using the snippet shown in the docs: https://graphql-dotnet.github.io/docs/migrations/migration3
[HttpPost]
public async Task<IActionResult> Post([FromBody] GraphQLQueryDTO query)
{
var result = await _executer.ExecuteAsync(_ =>
{
_.Schema = _schema;
_.Query = query.Query;
_.Inputs = query.Variables?.ToInputs();
});
/* ----------- Added this ---------------------------------*/
HttpContext.Response.ContentType = "application/json";
HttpContext.Response.StatusCode = 200; // OK
var writer = new GraphQL.SystemTextJson.DocumentWriter();
await writer.WriteAsync(HttpContext.Response.Body, result);*
/* ------------------------------------------------------*/
if (result.Errors?.Count > 0)
{
return BadRequest();
}
return Ok(result.Data);
}
}
In your startup.cs, in ConfigureServices
Add AddNewtonsoftJson() after AddControllers()
services.AddControllers().AddNewtonsoftJson();
We are trying to streamline our automatic updating fields for DateCreated, CreatedBy, LastDateModified, LastModifiedBy, DateDeleted and DeletedBy and worked OK by adding routine OnBeforeSaving in the ApplicationDBContext.cs and we also do the SOFT-DELETE for retaining the records (flagged as IsDeleted approach instead) when we deleted:
ApplicationDBContext.cs -
using System;
using System.Collections.Generic;
using System.Text;
using Microsoft.AspNetCore.Identity.EntityFrameworkCore;
using Microsoft.EntityFrameworkCore;
using AthlosifyWebArchery.Models;
using System.Linq;
using System.Threading;
using System.Threading.Tasks;
using System.Linq.Expressions;
using Microsoft.AspNetCore.Http;
using System.Security.Claims;
using Microsoft.AspNetCore.Identity;
namespace AthlosifyWebArchery.Data
{
public class ApplicationDbContext : IdentityDbContext<ApplicationUser, ApplicationRole, string,
IdentityUserClaim<string>,
ApplicationUserRole, IdentityUserLogin<string>,
IdentityRoleClaim<string>, IdentityUserToken<string>>
{
private readonly IHttpContextAccessor _httpContextAccessor;
public ApplicationDbContext(DbContextOptions<ApplicationDbContext> options,
IHttpContextAccessor httpContextAccessor
)
: base(options)
{
_httpContextAccessor = httpContextAccessor;
}
public DbSet<AthlosifyWebArchery.Models.TournamentBatchItem> TournamentBatchItem { get; set; }
public DbSet<AthlosifyWebArchery.Models.TournamentBatch> TournamentBatch { get; set; }
public virtual DbSet<AthlosifyWebArchery.Models.Host> Host { get; set; }
public DbSet<AthlosifyWebArchery.Models.HostApplicationUser> HostApplicationUser { get; set; }
public virtual DbSet<AthlosifyWebArchery.Models.Club> Club { get; set; }
public DbSet<AthlosifyWebArchery.Models.ClubApplicationUser> ClubApplicationUser { get; set; }
protected override void OnModelCreating(ModelBuilder builder)
{
base.OnModelCreating(builder);
foreach (var entityType in builder.Model.GetEntityTypes())
{
// 1. Add the IsDeleted property
entityType.GetOrAddProperty("IsDeleted", typeof(bool));
// 2. Create the query filter
var parameter = Expression.Parameter(entityType.ClrType);
// EF.Property<bool>(post, "IsDeleted")
var propertyMethodInfo = typeof(EF).GetMethod("Property").MakeGenericMethod(typeof(bool));
var isDeletedProperty = Expression.Call(propertyMethodInfo, parameter, Expression.Constant("IsDeleted"));
// EF.Property<bool>(post, "IsDeleted") == false
BinaryExpression compareExpression = Expression.MakeBinary(ExpressionType.Equal, isDeletedProperty, Expression.Constant(false));
// post => EF.Property<bool>(post, "IsDeleted") == false
var lambda = Expression.Lambda(compareExpression, parameter);
builder.Entity(entityType.ClrType).HasQueryFilter(lambda);
}
// Many to Many relationship - HostApplicationUser
builder.Entity<HostApplicationUser>()
.HasKey(bc => new { bc.HostID, bc.Id });
builder.Entity<HostApplicationUser>()
.HasOne(bc => bc.Host)
.WithMany(b => b.HostApplicationUsers)
.HasForeignKey(bc => bc.HostID);
builder.Entity<HostApplicationUser>()
.HasOne(bc => bc.ApplicationUser)
.WithMany(c => c.HostApplicationUsers)
.HasForeignKey(bc => bc.Id);
// Many to Many relationship - ClubApplicationUser
builder.Entity<ClubApplicationUser>()
.HasKey(bc => new { bc.ClubID, bc.Id });
builder.Entity<ClubApplicationUser>()
.HasOne(bc => bc.Club)
.WithMany(b => b.ClubApplicationUsers)
.HasForeignKey(bc => bc.ClubID);
builder.Entity<ClubApplicationUser>()
.HasOne(bc => bc.ApplicationUser)
.WithMany(c => c.ClubApplicationUsers)
.HasForeignKey(bc => bc.Id);
// Many to Many relationship - ApplicationUserRole
builder.Entity<ApplicationUserRole>(userRole =>
{
userRole.HasKey(ur => new { ur.UserId, ur.RoleId });
userRole.HasOne(ur => ur.Role)
.WithMany(r => r.UserRoles)
.HasForeignKey(ur => ur.RoleId)
.IsRequired();
userRole.HasOne(ur => ur.User)
.WithMany(r => r.ApplicationUserRoles)
.HasForeignKey(ur => ur.UserId)
.IsRequired();
});
}
public override int SaveChanges(bool acceptAllChangesOnSuccess)
{
OnBeforeSaving();
return base.SaveChanges(acceptAllChangesOnSuccess);
}
public override Task<int> SaveChangesAsync(bool acceptAllChangesOnSuccess, CancellationToken cancellationToken = default(CancellationToken))
{
OnBeforeSaving();
return base.SaveChangesAsync(acceptAllChangesOnSuccess, cancellationToken);
}
private void OnBeforeSaving()
{
if (_httpContextAccessor.HttpContext != null)
{
var userName = _httpContextAccessor.HttpContext.User.Identity.Name;
var userId = _httpContextAccessor.HttpContext.User.FindFirstValue(ClaimTypes.NameIdentifier);
// Added
var added = ChangeTracker.Entries().Where(v => v.State == EntityState.Added &&
typeof(IBaseEntity).IsAssignableFrom(v.Entity.GetType())).ToList();
added.ForEach(entry =>
{
((IBaseEntity)entry.Entity).DateCreated = DateTime.UtcNow;
((IBaseEntity)entry.Entity).CreatedBy = userId;
((IBaseEntity)entry.Entity).LastDateModified = DateTime.UtcNow;
((IBaseEntity)entry.Entity).LastModifiedBy = userId;
});
// Modified
var modified = ChangeTracker.Entries().Where(v => v.State == EntityState.Modified &&
typeof(IBaseEntity).IsAssignableFrom(v.Entity.GetType())).ToList();
modified.ForEach(entry =>
{
((IBaseEntity)entry.Entity).LastDateModified = DateTime.UtcNow;
((IBaseEntity)entry.Entity).LastModifiedBy = userId;
});
// Deleted
var deleted = ChangeTracker.Entries().Where(v => v.State == EntityState.Deleted &&
typeof(IBaseEntity).IsAssignableFrom(v.Entity.GetType())).ToList();
deleted.ForEach(entry =>
{
((IBaseEntity)entry.Entity).DateDeleted = DateTime.UtcNow;
((IBaseEntity)entry.Entity).DeletedBy = userId;
});
foreach (var entry in ChangeTracker.Entries()
.Where(e => e.State == EntityState.Deleted &&
e.Metadata.GetProperties().Any(x => x.Name == "IsDeleted")))
{
switch (entry.State)
{
case EntityState.Added:
entry.CurrentValues["IsDeleted"] = false;
break;
case EntityState.Deleted:
entry.State = EntityState.Modified;
entry.CurrentValues["IsDeleted"] = true;
break;
}
}
}
else
{
// DbInitializer kicks in
}
}
}
}
Worked OK with standalone model/class ie. Club, Host, ApplicationUser ** BUT not bridging model/class ie. **ClubApplicationUser and HostApplicationUser
So we have to do manually on this (AssignClub.cshtml.cs for instance) by adding manually for creating/deleting as you can see below:
// Removed the current one
var clubApplicationUserToRemove = await _context.ClubApplicationUser
.FirstOrDefaultAsync(m => m.Id == id.ToString()
&& m.ClubID == AssignClubUser.OriginalClubID);
clubApplicationUserToRemove.DateDeleted = DateTime.UtcNow;
clubApplicationUserToRemove.DeletedBy = userID;
_context.ClubApplicationUser.Remove(clubApplicationUserToRemove);
... the deleted.Count() below returning 0 ... and the same with creating etc:
// Deleted
var deleted = ChangeTracker.Entries().Where(v => v.State == EntityState.Deleted &&
typeof(IBaseEntity).IsAssignableFrom(v.Entity.GetType())).ToList();
deleted.ForEach(entry =>
{
((IBaseEntity)entry.Entity).DateDeleted = DateTime.UtcNow;
((IBaseEntity)entry.Entity).DeletedBy = userId;
});
AssignClub.cshtml.cs -
using System;
using System.Collections.Generic;
using System.Linq;
using System.Threading.Tasks;
using Microsoft.AspNetCore.Mvc;
using Microsoft.AspNetCore.Mvc.RazorPages;
using Microsoft.AspNetCore.Mvc.Rendering;
using Microsoft.EntityFrameworkCore;
using AthlosifyWebArchery.Data;
using AthlosifyWebArchery.Models;
using AthlosifyWebArchery.Pages.Administrators.TournamentBatches;
using Microsoft.AspNetCore.Identity;
using System.ComponentModel.DataAnnotations;
using static AthlosifyWebArchery.Pages.Administrators.Users.AssignClubUserModel;
using Microsoft.AspNetCore.Http;
using System.Security.Claims;
namespace AthlosifyWebArchery.Pages.Administrators.Users
{
//public class AssignClubUserModel : ClubNamePageModel
public class AssignClubUserModel : UserViewPageModel
{
private readonly AthlosifyWebArchery.Data.ApplicationDbContext _context;
private readonly IHttpContextAccessor _httpContextAccessor;
public AssignClubUserModel(AthlosifyWebArchery.Data.ApplicationDbContext context,
IHttpContextAccessor httpContextAccessor
)
{
_context = context;
_httpContextAccessor = httpContextAccessor;
}
public class AssignClubUserViewModel<ApplicationUser>
{
public string Id { get; set; }
public string FirstName { get; set; }
public string LastName { get; set; }
public string UserName { get; set; }
public Guid ClubID { get; set; }
public Guid OriginalClubID { get; set; }
public byte[] RowVersion { get; set; }
}
[BindProperty]
public AssignClubUserViewModel<ApplicationUser> AssignClubUser { get; set; }
//public SelectList ClubNameSL { get; set; }
public async Task<IActionResult> OnGetAsync(Guid? id)
{
if (id == null)
return NotFound();
AssignClubUser = await _context.Users
.Include(u => u.ClubApplicationUsers)
.Where(t => t.Id == id.ToString())
.Select(t => new AssignClubUserViewModel<ApplicationUser>
{
Id = t.Id,
FirstName = t.FirstName,
LastName = t.LastName,
UserName = t.UserName,
ClubID = t.ClubApplicationUsers.ElementAt(0).ClubID,
OriginalClubID = t.ClubApplicationUsers.ElementAt(0).ClubID,
RowVersion = t.ClubApplicationUsers.ElementAt(0).RowVersion
}).SingleAsync();
if (AssignClubUser == null)
return NotFound();
// Use strongly typed data rather than ViewData.
//ClubNameSL = new SelectList(_context.Club, "ClubID", "Name");
PopulateClubsDropDownList(_context, AssignClubUser.ClubID);
return Page();
}
public async Task<IActionResult> OnPostAsync(Guid id)
{
if (!ModelState.IsValid)
return Page();
// 1st approach:
// Modify the bridge model directly
/*var clubApplicationUserToUpdate = await _context.ClubApplicationUser
.FirstOrDefaultAsync(m => m.Id == id.ToString()
&& m.ClubID == AssignClubUser.OriginalClubID);
if (clubApplicationUserToUpdate == null)
return await HandleDeletedUser();
_context.Entry(clubApplicationUserToUpdate)
.Property("RowVersion").OriginalValue = AssignClubUser.RowVersion;
// This slightly tweek for this particular
// As the modified Change Track is not triggered
//_context.Entry(clubApplicationUserToUpdate)
// .Property("LastDateModified").CurrentValue = DateTime.UtcNow;
//_context.Entry(clubApplicationUserToUpdate)
// .Property("LastModifiedBy").CurrentValue = _httpContextAccessor.HttpContext.User.FindFirstValue(ClaimTypes.NameIdentifier);
if (await TryUpdateModelAsync<ClubApplicationUser>(
clubApplicationUserToUpdate,
"AssignClubUser",
s => s.Id, s => s.ClubID))
{
try
{
await _context.SaveChangesAsync();
return RedirectToPage("./Index");
}
catch (DbUpdateConcurrencyException ex)
{
var exceptionEntry = ex.Entries.Single();
var clientValues = (ClubApplicationUser)exceptionEntry.Entity;
var databaseEntry = exceptionEntry.GetDatabaseValues();
if (databaseEntry == null)
{
ModelState.AddModelError(string.Empty, "Unable to save. " +
"The club application user was deleted by another user.");
return Page();
}
var dbValues = (ClubApplicationUser)databaseEntry.ToObject();
await setDbErrorMessage(dbValues, clientValues, _context);
AssignClubUser.RowVersion = (byte[])dbValues.RowVersion;
ModelState.Remove("User.RowVersion");
}
}
*/
// 2nd approach:
// Soft -Delete and Add
// Did the soft-deleting and managed to add a new one BUT then die the roll back (adding the old one)
// Result: Violation of PRIMARY KEY constraint 'PK_ClubApplicationUser'.
// Cannot insert duplicate key in object
// Due to duplicate key
/*var clubApplicatonUserToRemove = await _context.ClubApplicationUser
.FirstOrDefaultAsync(m => m.Id == id.ToString());
ClubApplicationUser clubApplicatonUserToAdd = new ClubApplicationUser();
clubApplicatonUserToAdd.Id = id.ToString();
clubApplicatonUserToAdd.ClubID = AssignClubUser.SelectedClubID;
//_context.Entry(clubApplicatonUserToRemove)
// .Property("RowVersion").OriginalValue = User.RowVersion;
if (clubApplicatonUserToRemove != null)
{
_context.ClubApplicationUser.Remove(clubApplicatonUserToRemove);
await _context.SaveChangesAsync();clubApplicationUserDeleted
_context.ClubApplicationUser.Add(clubApplicatonUserToAdd);
await _context.SaveChangesAsync();
}*/
//delete all club memberships and add new one
//var clubApplicationUserDeleted = await _context.ClubApplicationUser
// .FirstOrDefaultAsync(m => m.Id == id.ToString()
// && m.ClubID == AssignClubUser.ClubID && m.IsDeleted == );
var userID = _httpContextAccessor.HttpContext.User.FindFirstValue(ClaimTypes.NameIdentifier);
if (AssignClubUser.ClubID != AssignClubUser.OriginalClubID)
{
var deletedClubApplicationUsers = _context.ClubApplicationUser.IgnoreQueryFilters()
.Where(post => post.Id == id.ToString()
&& post.ClubID == AssignClubUser.ClubID && EF.Property<bool>(post, "IsDeleted") == true);
if (deletedClubApplicationUsers.Count() > 0)
{
// Undo the deleted one
foreach (var deletedClubApplicationUser in deletedClubApplicationUsers)
{
var postEntry = _context.ChangeTracker.Entries<ClubApplicationUser>().First(entry => entry.Entity == deletedClubApplicationUser);
postEntry.Property("IsDeleted").CurrentValue = false;
postEntry.Property("LastDateModified").CurrentValue = DateTime.UtcNow;
postEntry.Property("LastModifiedBy").CurrentValue = userID;
postEntry.Property("DateDeleted").CurrentValue = null;
postEntry.Property("DeletedBy").CurrentValue = null;
}
// Removed the current one
var clubApplicationUserToRemove = await _context.ClubApplicationUser
.FirstOrDefaultAsync(m => m.Id == id.ToString()
&& m.ClubID == AssignClubUser.OriginalClubID);
clubApplicationUserToRemove.DateDeleted = DateTime.UtcNow;
clubApplicationUserToRemove.DeletedBy = userID;
_context.ClubApplicationUser.Remove(clubApplicationUserToRemove);
try
{
await _context.SaveChangesAsync();
return RedirectToPage("./Index");
}
catch
{
PopulateClubsDropDownList(_context, AssignClubUser.ClubID);
return Page();
}
}
else
{
// Removed the current one
var clubApplicationUserToRemove = await _context.ClubApplicationUser
.FirstOrDefaultAsync(m => m.Id == id.ToString()
&& m.ClubID == AssignClubUser.OriginalClubID);
clubApplicationUserToRemove.DateDeleted = DateTime.UtcNow;
clubApplicationUserToRemove.DeletedBy = userID;
_context.ClubApplicationUser.Remove(clubApplicationUserToRemove);
try
{
_context.Entry(clubApplicationUserToRemove).State = EntityState.Deleted;
await _context.SaveChangesAsync();
}
catch
{
PopulateClubsDropDownList(_context, AssignClubUser.ClubID);
return Page();
}
// Added the new one
var newClubApplicationUser = new ClubApplicationUser()
{
Id = id.ToString(),
ClubID = AssignClubUser.ClubID,
DateCreated = DateTime.UtcNow,
CreatedBy = userID,
LastDateModified = DateTime.UtcNow,
LastModifiedBy = userID
};
_context.ClubApplicationUser.Add(newClubApplicationUser);
try
{
_context.Entry(newClubApplicationUser).State = EntityState.Added;
await _context.SaveChangesAsync();
return RedirectToPage("./Index");
}
catch
{
PopulateClubsDropDownList(_context, AssignClubUser.ClubID);
return Page();
}
}
}
return RedirectToPage("./Index");
}
private async Task<IActionResult> HandleDeletedUser()
{
ClubApplicationUser deletedClubApplicationUser = new ClubApplicationUser();
ModelState.AddModelError(string.Empty,
"Unable to save. The club was deleted by another user.");
return Page();
}
private async Task setDbErrorMessage(ClubApplicationUser dbValues,
ClubApplicationUser clientValues, ApplicationDbContext context)
{
ModelState.AddModelError(string.Empty,
"The record you attempted to edit "
+ "was modified by another user after you. The "
+ "edit operation was canceled and the current values in the database "
+ "have been displayed. If you still want to edit this record, click "
+ "the Save button again.");
}
}
}
ClubApplicationUser.cs model:
using System;
using System.Collections.Generic;
using System.ComponentModel.DataAnnotations;
using System.ComponentModel.DataAnnotations.Schema;
using System.Linq;
using System.Threading.Tasks;
namespace AthlosifyWebArchery.Models
{
public class ClubApplicationUser
{
public Guid ClubID { get; set; }
public Club Club { get; set; }
public string Id { get; set; }
public ApplicationUser ApplicationUser { get; set; }
public DateTime DateCreated { get; set; }
public string CreatedBy { get; set; }
public DateTime LastDateModified { get; set; }
public string LastModifiedBy { get; set; }
public DateTime? DateDeleted { get; set; }
public string DeletedBy { get; set; }
public bool IsDeleted { get; set; }
[Timestamp]
public byte[] RowVersion { get; set; }
[ForeignKey("CreatedBy")]
public ApplicationUser ClubApplicationCreatedUser { get; set; }
[ForeignKey("LastModifiedBy")]
public ApplicationUser ClubApplicationLastModifiedUser { get; set; }
}
}
Any ideas? The current solution is working OK BUT it just has extra routines to add these DateCreated, CreatedBy, LastDateModified, LastModifiedBy, DateDeleted and DeletedBy manually for this bridging model/class.
Environment:
.NET Core 2.2
SQL Server
Updates 1:
We even add this before the saving and it didn't trigger the automatic ChangeTracker.Entries():
_context.Entry(newClubApplicationUser).State = EntityState.Deleted;
await _context.SaveChangesAsync();
Updates 2:
Added the ClubApplicationUser model above.
I wants to create a new user with role. I'm using entity migration. so i get the all default tables. Without roles I'm able to register a new user but when i add the roles drop down I'm facing a error Cannot convert from string to ourproject.entitites.user
Please help and teach me where i did the mistake. I'm new to .net core technology.
I'm facing a problem with user.Id
await _userManager.AddToRoleAsync(user.Id, model.UserRoles);
ControllerCode:
[HttpPost, ValidateAntiForgeryToken]
public async Task<IActionResult> Register(RegisterUserModel model)
{
if (ModelState.IsValid)
{
var user = new User { UserName = model.Username, Email = model.Email,
PhoneNumber = model.PhoneNumber };
//var phoneNo = new User { PhoneNumber = model.PhoneNumber };
var createResult = await _userManager.CreateAsync(user, model.Password);
if (createResult.Succeeded)
{
await _userManager.AddToRoleAsync(user.Id, model.UserRoles);
return RedirectToAction("Details", "Home");
}
else
{
ViewBag.Name = new SelectList(_Context.Roles.Where(u => !u.Name.Contains("Admin"))
.ToList(), "Name", "Name");
foreach (var error in createResult.Errors)
{
ModelState.AddModelError("", error.Description);
}
}
}
return View();
}
Registermodel Code:
public class RegisterUserModel
{
[Required,MaxLength(256)]
public string Username { get; set; }
[Required, DataType(DataType.Password)]
public string Password { get; set; }
[Required, DataType(DataType.Password), Compare(nameof(Password))]
public string ConfirmPassword { get; set; }
[Required, DataType(DataType.EmailAddress),MaxLength(256)]
public string Email { get; set; }
[Required,DataType(DataType.PhoneNumber)]
public string PhoneNumber { get; set; }
[Display(Name ="UserRoles")]
public string UserRoles { get; set; }
}
User Entity Code:
using Microsoft.AspNetCore.Identity;
namespace OurProject.Entities
{
public class User : IdentityUser
{
}
}
Here is a functional code for .net core 2.0. In AddToRoleAsync method I am sending user object itself.
[AllowAnonymous]
[ValidateAntiForgeryToken]
public async Task<IActionResult> Register(RegisterViewModel model, string returnUrl = null)
{
ViewData["ReturnUrl"] = returnUrl;
if (ModelState.IsValid)
{
var user = new ApplicationUser { UserName = model.Email, Email = model.Email };
var result = await _userManager.CreateAsync(user, model.Password);
if (result.Succeeded)
{
string roleName = "Manager";
var isExistRole = await this._roleManager.RoleExistsAsync(roleName);
if (!isExistRole)
{
await this._roleManager.CreateAsync(
new IdentityRole
{
Id = Guid.NewGuid().ToString(),
Name = roleName
});
}
await _userManager.AddToRoleAsync(user, roleName);
return RedirectToLocal(returnUrl);
}
AddErrors(result);
}
// If we got this far, something failed, redisplay form
return View(model);
}
My authentication is working fine on it is own but i need to use phoneNumber of users instead of user names.
There is my Provider class
using Identity.Infrastructure;
using Microsoft.AspNet.Identity.Owin;
using Microsoft.Owin.Security;
using Microsoft.Owin.Security.OAuth;
using System.Security.Claims;
using System.Threading.Tasks;
namespace Identity.Providers
{
public class CustomOAuthProvider : OAuthAuthorizationServerProvider
{
public override Task ValidateClientAuthentication(OAuthValidateClientAuthenticationContext context)
{
context.Validated();
return Task.FromResult<object>(null);
}
public override async Task GrantResourceOwnerCredentials(OAuthGrantResourceOwnerCredentialsContext context)
{
var allowedOrigin = "*";
context.OwinContext.Response.Headers.Add("Access-Control-Allow-Origin", new[] { allowedOrigin });
var userManager = context.OwinContext.GetUserManager<ApplicationUserManager>();
ApplicationUser user = await userManager.FindAsync(context.UserName, context.Password);
if (user == null)
{
context.SetError("invalid_grant", "The user name or password is incorrect.");
return;
}
if (!user.EmailConfirmed)
{
context.SetError("invalid_grant", "User did not confirm email.");
return;
}
ClaimsIdentity oAuthIdentity = await user.GenerateUserIdentityAsync(userManager, "JWT");
var ticket = new AuthenticationTicket(oAuthIdentity, null);
context.Validated(ticket);
}
}
}
in this class context is coming with only userName and Password,so it cant reach PhoneNumber even i send it as a parameter.I think problem will solve after if i can change
userManager.FindAsync(context.UserName, context.Password)
like this
userManager.FindAsync(context.PhoneNumber, context.Password)
VS doesn't allow me to interfere OAuthGrantResourceOwnerCredentialsContext
using Identity.Infrastructure;
using Microsoft.AspNet.Identity.EntityFramework;
using System;
using System.Collections.Generic;
using System.Net.Http;
using System.Web.Http.Routing;
namespace Identity.Models
{
public class ModelFactory
{
private UrlHelper _UrlHelper;
private ApplicationUserManager _AppUserManager;
public ModelFactory(HttpRequestMessage request, ApplicationUserManager appUserManager)
{
_UrlHelper = new UrlHelper(request);
_AppUserManager = appUserManager;
}
public UserReturnModel Create(ApplicationUser appUser)
{
return new UserReturnModel
{
Url = _UrlHelper.Link("GetUserById", new { id = appUser.Id }),
Id = appUser.Id,
UserName = appUser.UserName,
FullName = string.Format("{0} {1}", appUser.FirstName, appUser.LastName),
Email = appUser.Email,
EmailConfirmed = true,
Level = appUser.Level,
JoinDate = appUser.JoinDate,
Roles = _AppUserManager.GetRolesAsync(appUser.Id).Result,
Claims = _AppUserManager.GetClaimsAsync(appUser.Id).Result,
PhoneNumber = appUser.PhoneNumber
};
}
public RoleReturnModel Create(IdentityRole appRole)
{
return new RoleReturnModel
{
Url = _UrlHelper.Link("GetRoleById", new { id = appRole.Id }),
Id = appRole.Id,
Name = appRole.Name
};
}
}
public class RoleReturnModel
{
public string Url { get; set; }
public string Id { get; set; }
public string Name { get; set; }
}
public class UserReturnModel
{
public string Url { get; set; }
public string Id { get; set; }
public string UserName { get; set; }
public string FullName { get; set; }
public string PhoneNumber { get; set; }
public string Email { get; set; }
public bool EmailConfirmed { get; set; }
public int Level { get; set; }
public DateTime JoinDate { get; set; }
public IList<string> Roles { get; set; }
public IList<System.Security.Claims.Claim> Claims { get; set; }
}
}
As result I stucked on authenticating with phoneNumber instead of userName and set deviceId as password
public override Task<ApplicationUser> FindAsync(string Phone, string password)
{
//Do your Stuff here
//return base.FindAsync(userName, password);
}
Overrride FIndAsync() in the IndentityConfig.cs
So I have followed this tutorial
https://world.episerver.com/blogs/Duong-Nguyen/Dates/2014/1/Country-Region-drop-down-lists-in-All-properties-mode/
in my effort to create dependent drop downs.
public class LocationBlock : BlockData
{
[SelectOne(SelectionFactoryType = typeof(CountrySelectionFactory))]
public virtual string Country { get; set; }
[SelectOne(SelectionFactoryType = typeof(RegionSelectionFactory))]
[ClientEditor(ClientEditingClass = "alloy/editors/FilterableSelectionEditor", SelectionFactoryType = typeof(RegionSelectionFactory))]
public virtual string Region { get; set; }
}
public class ArticlePage : StandardPage
{
[Display(GroupName = "IndexData")]
public virtual LocationBlock Location { get; set; }
}
class CountrySelectionFactory : ISelectionFactory
{
public IEnumerable GetSelections(ExtendedMetadata metadata)
{
return new Country[]
{
new Country() { CountryCode = "US", Name = "United States" },
new Country() { CountryCode = "SE", Name = "Sweden" }
};
}
}
class RegionSelectionFactory : ISelectionFactory
{
public IEnumerable GetSelections(ExtendedMetadata metadata)
{
return new Region[]
{
new Region() { CountryCode = "US", RegionCode = "NY", Name = "New York" },
new Region() { CountryCode = "US", RegionCode = "CA", Name = "California" },
new Region() { CountryCode = "SE", RegionCode = "AB", Name = "Stockholm" },
new Region() { CountryCode = "SE", RegionCode = "O", Name = "Västra Götaland" }
};
}
}
class Country : ISelectItem
{
public string CountryCode { get; set; }
public string Name { get; set; }
public string Text
{
get
{
return Name;
}
}
public object Value
{
get
{
return CountryCode;
}
}
}
class Region : ISelectItem
{
public string CountryCode { get; set; }
public string RegionCode { get; set; }
public string Name { get; set; }
public string Text
{
get
{
return Name;
}
}
public object Value
{
get
{
return String.Format("{0}-{1}", CountryCode, RegionCode);
}
}
}
[EditorDescriptorRegistration(TargetType = typeof(LocationBlock))]
public class LocationBlockEditorDescriptor : EditorDescriptor
{
public override void ModifyMetadata(ExtendedMetadata metadata, IEnumerable attributes)
{
base.ModifyMetadata(metadata, attributes);
metadata.Properties.Cast().First().GroupSettings.ClientLayoutClass = "alloy/LocationBlockContainer";
metadata.Properties.Cast().First().ClientEditingClass = "alloy/editors/FilterableSelectionEditor";
}
}
After that I created a ClientResources folder in my root. Under that I created Scripts folder and placed LocationBlockConatiner there and under Scripts I created another folder named Editors and placed my FilterableSelectionEditor there.
I created a module.config file and the code in it looks like this
LocationBlockContainer is this
define([
"dojo/_base/declare",
"dojo/_base/lang",
"epi/shell/layout/SimpleContainer"
],
function (
declare,
lang,
SimpleContainer
) {
return declare([SimpleContainer], {
countryDropdown: null,
regionDropdown: null,
addChild: function (child) {
// Summar: Add a widget to the container
this.inherited(arguments);
if (child.name.indexOf("country") >= 0) {
// If it's the country drop down list
this.countryDropdown = child;
// Connect to change event to update the region drop down list
this.own(this.countryDropdown.on("change", lang.hitch(this, this._updateRegionDropdown)));
} else if (child.name.indexOf("region") >= 0) {
// If it's the region drop down list
this.regionDropdown = child;
// Update the region drop down
this._updateRegionDropdown(this.countryDropdown.value);
}
},
_updateRegionDropdown: function (country) {
console.log(1);
if (country !== "" && this.previousCountry === "") {
this.previousCountry = country;
}
// Clear the current value
if (country !== this.previousCountry) {
this.regionDropdown.set("value", null);
this.previousCountry = country;
}
console.log(this.regionDropdown);
// Set the filter
this.regionDropdown.set("filter", function (region) {
console.log(region);
return region.value.indexOf(country) === 0;
});
}
});
});
FilterableSelectionEditor.js is this
define([
"dojo/_base/declare",
"dojo/_base/array",
"epi-cms/contentediting/editors/SelectionEditor"
],
function (
declare,
array,
SelectionEditor
) {
return declare([SelectionEditor], {
_allOptions: null,
filter: null,
_setOptionsAttr: function (options) {
// summary: set the options
this._allOptions = options;
this.inherited(arguments, [array.filter(options, this.filter || function () {
// return all options if no filter function provided.
return true;
}, this)]);
},
_setFilterAttr: function (filter) {
// summary: set the option filter function
this._set("filter", filter);
this.set("options", this._allOptions);
}
});
});
It doesn't seem to work. When I select Country the Regions are emptied and then every region is added again because the end result shows all the regions no matter what country I have selected.
Any help here would be really appreciated.