How join List<Class> with IEnumerable<Class>? - linq

I have the following classes.
public class Course
{
public string CourseNumber { get; set; }
public List<PriceGroup> PriceGroups { get; set; }
}
public class PriceGroup
{
public int Id { get; set; }
public string CourseNumber { get; set; }
}
I have a service that fetches data from the database. Courses is an IEnumerable<Course> and priceGroups is a IEnumerable<PriceGroup>. As you can see in the Course class, a course has a List<PriceGroup>. I need to somehow add each PriceGroup in the IEnumerable<PriceGroup> to the list in each course in the IEnumerable<Course> if they match on the variable CourseNumber. So I will end up with a correct Course containing the right List<PriceGroup>'s
public async Task<IEnumerable<Course>> GetAllAsync()
{
//courses is an IEnumerable<Course>
var courses = await _courseRepository.GetAllAsync();
//priceGroups is an IEnumerable<PriceGroup>
var priceGroups = await _coursePriceGroupRepository.GetAllAsync();
courses.GroupJoin(priceGroups, c => c.PriceGroups.Select(i => i.CourseNumber), pg => pg.CourseNumber, (c, pg) => new
{
});
return await courses;
}
To do this I've tried to do a GroupJoin but without success. I have not finished the attempt as I am completely stuck mentally. Perhaps I am trying to do something that is not possible with a GroupJoin. Does anyone know a way to achieve what I need?

So you want all (or some) Courses, each Course with all (or some of) its PriceGroups.
There is a straightforware one-to-many relation betweeen Courses and PriceGroups: Every Course has zero or more PriceGroups, every PriceGroup belongs to exactly one Course, namely the Course that foreign key CourseNumber refers to.
You are right, whenever you want items with their sub-items you use GroupJoin.
public async Task<IEnumerable<Course>> GetAllAsync()
{
IEnumerable<Course> courses = await _courseRepository.GetAllAsync();
IEnumerable<PriceGroup> priceGroups = await _coursePriceGroupRepository.GetAllAsync();
var queryCoursesWithPriceGroups = courses.GroupJoin( // GroupJoin Courses
priceGroups, // with PriceGroups
course => course.CourseNumber, // from every Course take primary key
priceGroup => priceGroup.CourseNumber, // from every PriceGroup take foreign key
(course, priceGroupsOfThisCourse) => new // from every Course with
{ // its priceGroups make one new
// Select the Course properties you plan to use:
Id = course.Id,
Name = course.Name,
StartDate = course.StartDate,
...
PriceGroups = priceGroupsOfThisCourse.Select(priceGroup => new
{
// Select only the PriceGroup properties that you plan to use
Id = priceGroup.Id,
Name = priceGroup.Name,
...
// not needed, you know the value:
// CourseId = priceGroup.CourseId
})
.ToList(),
});
// Note: this is a query. It is not executed yet!
return await queryCoursesWithPriceGroups.ToListAsync();

//Assuming courseNumber is unique in Courses table
return Courses.Select( c=> { c.PriceGroup=priceGroup.Where(p=>p.CourseNumber==c.CourseNumber).ToList(); return c; });

I did a practical example, obviously you should substitute the fake methods with repository ones:
class Program
{
static async void Main(string[] args)
{
var courses = GetAllAsync().Result;
}
private static async Task<IEnumerable<Course>> GetAllAsync()
{
//courses is an IEnumerable<Course>
var courses = await GetCoursesAsync();
//priceGroups is an IEnumerable<PriceGroup>
var priceGroups = await GetPriceGroups();
foreach (var course in courses)
{
foreach (var priceGroup in priceGroups.Where(x => x.CourseNumber == course.CourseNumber))
{
course.PriceGroups.Add(priceGroup);
}
}
return courses;
}
private static async Task<IEnumerable<Course>> GetCoursesAsync()
{
return await Task.FromResult<IEnumerable<Course>>(
new List<Course>() {
new Course{
CourseNumber = "PIZZA1",
PriceGroups = new List<PriceGroup>()
},
new Course{
CourseNumber = "PIZZA2",
PriceGroups = new List<PriceGroup>()
},
new Course{
CourseNumber = "PIZZA3",
PriceGroups = new List<PriceGroup>()
},
}
);
}
private static async Task<IEnumerable<PriceGroup>> GetPriceGroups()
{
return await Task.FromResult<IEnumerable<PriceGroup>>(
new List<PriceGroup>() {
new PriceGroup{
Id = 1,
CourseNumber = "PIZZA1"
},
new PriceGroup{
Id = 2,
CourseNumber = "PIZZA2"
},
new PriceGroup{
Id = 3,
CourseNumber = "PIZZA3"
}
}
);
}
}
public class Course
{
public string CourseNumber { get; set; }
public List<PriceGroup> PriceGroups { get; set; }
}
public class PriceGroup
{
public int Id { get; set; }
public string CourseNumber { get; set; }
}
You should avoid using IEnumerable when you return data from DB, you can incur in unexpected behaviours. I suggest you this approach:
class Program
{
static void Main(string[] args)
{
var courses = GetAllAsync().Result;
}
private static async Task<List<Course>> GetAllAsync()
{
var courses = await GetCoursesAsync();
var priceGroups = await GetPriceGroups();
courses.ForEach(x => { x.PriceGroups.AddRange(priceGroups.Where(y => y.CourseNumber == x.CourseNumber)); });
return courses;
}
private static async Task<List<Course>> GetCoursesAsync()
{
return await Task.FromResult(
new List<Course>() {
new Course{
CourseNumber = "PIZZA1",
PriceGroups = new List<PriceGroup>()
},
new Course{
CourseNumber = "PIZZA2",
PriceGroups = new List<PriceGroup>()
},
new Course{
CourseNumber = "PIZZA3",
PriceGroups = new List<PriceGroup>()
},
}
);
}
private static async Task<List<PriceGroup>> GetPriceGroups()
{
return await Task.FromResult(
new List<PriceGroup>() {
new PriceGroup{
Id = 1,
CourseNumber = "PIZZA1"
},
new PriceGroup{
Id = 2,
CourseNumber = "PIZZA2"
},
new PriceGroup{
Id = 3,
CourseNumber = "PIZZA3"
}
}
);
}
}
public class Course
{
public string CourseNumber { get; set; }
public List<PriceGroup> PriceGroups { get; set; }
}
public class PriceGroup
{
public int Id { get; set; }
public string CourseNumber { get; set; }
}
I made a version using GroupJoin too:
class Program
{
static void Main(string[] args)
{
var courses = GetAllAsync().Result;
}
private static async Task<List<Course>> GetAllAsync()
{
var courses = await GetCoursesAsync();
var priceGroups = await GetPriceGroups();
var groupedData = courses.GroupJoin(priceGroups,
course => course.CourseNumber,
priceGroup => priceGroup.CourseNumber,
(course, priceGroupsCollection) =>
new
{
CourseNumber = course.CourseNumber,
PriceGroups = priceGroupsCollection.ToList()
});
courses.ForEach(x => { x.PriceGroups = groupedData.FirstOrDefault(y => y.CourseNumber == x.CourseNumber)?.PriceGroups ?? new List<PriceGroup>(); });
return courses;
}
private static async Task<List<Course>> GetCoursesAsync()
{
return await Task.FromResult(
new List<Course>() {
new Course{
CourseNumber = "PIZZA1",
PriceGroups = new List<PriceGroup>()
},
new Course{
CourseNumber = "PIZZA2",
PriceGroups = new List<PriceGroup>()
},
new Course{
CourseNumber = "PIZZA3",
PriceGroups = new List<PriceGroup>()
},
}
);
}
private static async Task<List<PriceGroup>> GetPriceGroups()
{
return await Task.FromResult(
new List<PriceGroup>() {
new PriceGroup{
Id = 1,
CourseNumber = "PIZZA1"
},
new PriceGroup{
Id = 2,
CourseNumber = "PIZZA2"
},
new PriceGroup{
Id = 3,
CourseNumber = "PIZZA3"
},
new PriceGroup{
Id = 4,
CourseNumber = "PIZZA1"
}
}
);
}
}
public class Course
{
public string CourseNumber { get; set; }
public List<PriceGroup> PriceGroups { get; set; }
}
public class PriceGroup
{
public int Id { get; set; }
public string CourseNumber { get; set; }
}

Related

SonarQube ignoring AbstractValidator test classes

SonarQube ignores tests for AbstractValidators (FluentValidation.AspNetCore nuget package). In the coverage section, all RuleFor lines are marked as not covered.
What do I need to do, for the tests to be included in the coverage in SonarQube?
ReSharper shows the validator as being fully covered by tests.
The validator:
public class UserValidator : AbstractValidator<User>
{
public UserValidator()
{
RuleFor(a => a.Name).NotEmpty();
RuleFor(a => a.FirstName).NotEmpty();
}
}
Test class:
public class UserValidatorTests
{
[Test, TestCaseSource(nameof(User_InvalidData))]
public async Task ValidationShouldFail(UserAndErrors testData)
{
var result = await new UserValidator().TestValidateAsync(testData.User!);
Assert.IsFalse(result.IsValid);
Assert.AreEqual(result.Errors.Count, testData.ErrorsCount);
}
[Test]
public async Task ValidationShouldPass()
{
var request = new User
{
FirstName = "John",
Name= "Doe"
};
var result = await new UserValidator().TestValidateAsync(request);
Assert.IsTrue(result.IsValid);
result.ShouldNotHaveAnyValidationErrors();
}
private static IEnumerable<TestCaseData> User_InvalidData()
{
yield return new TestCaseData(new User
{
User = new User(),
ErrorsCount = 2
});
yield return new TestCaseData(new User
{
User = new User
{
Name = "Doe"
},
ErrorsCount = 1
});
yield return new TestCaseData(new User
{
User = new User
{
FirstName = "John"
},
ErrorsCount = 1
});
}
public class UserAndErrors
{
public User? User { get; set; }
public int ErrorsCount { get; set; } = 0;
}
}
User class:
public class User
{
public string Name { get; set; } = string.Empty;
public string FirstName { get; set; } = string.Empty;
}
The issue was the fact that the test project did not reference the coverlet.msbuild nuget package. Without it, SonarQube could not run the tests correctly and could not update the code coverage.

Cannot seed database with data

I wrote a class to seed the database with data. When debugging it it stacks on await IdentitySeedData.EnsurePopulated(service); Probably the problem is with usermanager.createasync(). Take a look:
namespace SportStore1
{
public class Program
{
public static void Main(string[] args)
{
var Host = BuildWebHost(args);
var Scopes = Host.Services.GetRequiredService<IServiceScopeFactory>();
using (var scope = Scopes.CreateScope())
{
var service = scope.ServiceProvider;
SeedDataFunc(service);
SeedIdentityDataFunc(service);
}
Host.Run();
}
public static void SeedDataFunc(IServiceProvider service)
{
SeedData.EnsurePopulated(service);
}
public static async void SeedIdentityDataFunc(IServiceProvider service)
{
await IdentitySeedData.EnsurePopulated(service);
}
public static IWebHost BuildWebHost(string[] args) =>
WebHost.CreateDefaultBuilder(args)
.UseStartup<Startup>()
.Build();
/*
public static void Main(string[] args)
{
CreateWebHostBuilder(args).Build().Run();
}
public static IWebHostBuilder CreateWebHostBuilder(string[] args) =>
WebHost.CreateDefaultBuilder(args)
.UseStartup<Startup>();
*/
}
}
namespace SportStore1.Data
{
public static class IdentitySeedData
{
public static async Task EnsurePopulated(IServiceProvider service)
{
UserManager<IdentityUser> userManager = service.GetRequiredService<UserManager<IdentityUser>>();
IConfiguration configuration = service.GetRequiredService<IConfiguration>();
ApplicationDbContext dbContext = service.GetRequiredService<ApplicationDbContext>();
if (!dbContext.Roles.Any())
{
foreach (var role in Roles)
{
dbContext.Add(role);
await dbContext.SaveChangesAsync();
}
}
if (!dbContext.Users.Any())
{
var user = new IdentityUser() { UserName = configuration["Email"], Email = configuration["Email"], EmailConfirmed = true };
await userManager.CreateAsync(user, configuration["Password"]);
}
if (!dbContext.UserRoles.Any())
{
var roleID = dbContext.Roles.Where(p => p.Name == "Administrator").FirstOrDefault().Id;
var userID = dbContext.Users.Where(p => p.Email == configuration["Email"]).FirstOrDefault().Id;
var userRole = new IdentityUserRole<string>()
{
RoleId = roleID,
UserId = userID
};
dbContext.UserRoles.Add(userRole);
await dbContext.SaveChangesAsync();
}
}
private static List<IdentityRole> Roles = new List<IdentityRole>()
{
new IdentityRole { Name = "Administrator", NormalizedName = "Administrator", ConcurrencyStamp = Guid.NewGuid().ToString() },
new IdentityRole { Name = "Manager", NormalizedName = "Manager", ConcurrencyStamp = Guid.NewGuid().ToString() },
new IdentityRole { Name = "User", NormalizedName = "User", ConcurrencyStamp = Guid.NewGuid().ToString()}
};
}
}
It stacks on await IdentitySeedData.EnsurePopulated(service) with a message System.ObjectDisposedException: 'Cannot access a disposed object.
Object name: 'UserManager`1'.' Any solution?
You need to await the async method. It seems you're getting stuck on the fact that Main is synchronous, but you can change that. In C# 7.2+ you can actually just have an async main:
public static async Task Main(string[] args)
In lesser versions, you'd just do:
public static void Main(string[] args) =>
MainAsync(args).GetAwaiter().GetResult();
public static async Task MainAsync(string[] args)
{
...
}
The compiler just builds that for you under the hood when you use async Main, anyways. Remember that you'll need to also do await host.RunAsync();, if you do this.
That said, this is not the method for doing database seeding any more. See the documentation.
You also need to use using() statement to ensure that the dbcontext is disposed as soon as it goes out of scope.
I tried your code in a brand new asp.net core 2.0 Individual User Account which uses ApplicationUser and it works well after using using block:
public static class IdentitySeedData
{
public static async Task EnsurePopulated(IServiceProvider service)
{
UserManager<ApplicationUser> userManager = service.GetRequiredService<UserManager<ApplicationUser>>();
IConfiguration configuration = service.GetRequiredService<IConfiguration>();
using (var dbContext = service.GetRequiredService<ApplicationDbContext>())
{
//ApplicationDbContext dbContext = service.GetRequiredService<ApplicationDbContext>();
if (!dbContext.Roles.Any())
{
foreach (var role in Roles)
{
dbContext.Add(role);
await dbContext.SaveChangesAsync();
}
}
if (!dbContext.Users.Any())
{
var user = new ApplicationUser() { UserName = configuration["Email"], Email = configuration["Email"], EmailConfirmed = true };
await userManager.CreateAsync(user, configuration["Password"]);
}
if (!dbContext.UserRoles.Any())
{
var roleID = dbContext.Roles.Where(p => p.Name == "Administrator").FirstOrDefault().Id;
var userID = dbContext.Users.Where(p => p.Email == configuration["Email"]).FirstOrDefault().Id;
var userRole = new IdentityUserRole<string>()
{
RoleId = roleID,
UserId = userID
};
dbContext.UserRoles.Add(userRole);
await dbContext.SaveChangesAsync();
}
}
}
private static List<IdentityRole> Roles = new List<IdentityRole>()
{
new IdentityRole { Name = "Administrator", NormalizedName = "Administrator", ConcurrencyStamp = Guid.NewGuid().ToString() },
new IdentityRole { Name = "Manager", NormalizedName = "Manager", ConcurrencyStamp = Guid.NewGuid().ToString() },
new IdentityRole { Name = "User", NormalizedName = "User", ConcurrencyStamp = Guid.NewGuid().ToString()}
};
}
Startup:
public void ConfigureServices(IServiceCollection services)
{
services.AddDbContext<ApplicationDbContext>(options =>
options.UseSqlServer(Configuration.GetConnectionString("DefaultConnection")));
services.AddIdentity<ApplicationUser, IdentityRole>()
.AddEntityFrameworkStores<ApplicationDbContext>()
.AddDefaultTokenProviders();
services.AddTransient<IEmailSender, EmailSender>();
services.AddMvc();
}
Refer to Cannot access a disposed object in ASP.NET Core when injecting DbContext

Sign-in user via remote services and about TokenAuthController

I need to sign-in the user using only remote services. I think using TokenAuthController in Web.Core application
I really can't understand why the snippet given below doesn't work. I have added a new method called Login in TokenAuthController .
using System;
using System.Collections.Generic;
using System.IdentityModel.Tokens.Jwt;
using System.Linq;
using System.Security.Claims;
using System.Threading.Tasks;
using Microsoft.AspNetCore.Identity;
using Microsoft.AspNetCore.Mvc;
using Abp.Authorization;
using Abp.Authorization.Users;
using Abp.MultiTenancy;
using Abp.Runtime.Security;
using Abp.UI;
using Abp.Web.Models;
using Microsoft.AspNetCore.Authorization;
using TSE.DergiAbone.Authentication.External;
using TSE.DergiAbone.Authentication.JwtBearer;
using TSE.DergiAbone.Authorization;
using TSE.DergiAbone.Authorization.Users;
using TSE.DergiAbone.Identity;
using TSE.DergiAbone.Models.TokenAuth;
using TSE.DergiAbone.MultiTenancy;
namespace TSE.DergiAbone.Controllers
{
[Route("api/[controller]/[action]")]
public class TokenAuthController : DergiAboneControllerBase
{
private readonly LogInManager _logInManager;
private readonly SignInManager _signInManager;
private readonly ITenantCache _tenantCache;
private readonly AbpLoginResultTypeHelper _abpLoginResultTypeHelper;
private readonly TokenAuthConfiguration _configuration;
private readonly IExternalAuthConfiguration _externalAuthConfiguration;
private readonly IExternalAuthManager _externalAuthManager;
private readonly UserRegistrationManager _userRegistrationManager;
public TokenAuthController(
LogInManager logInManager,
SignInManager signInManager,
ITenantCache tenantCache,
AbpLoginResultTypeHelper abpLoginResultTypeHelper,
TokenAuthConfiguration configuration,
IExternalAuthConfiguration externalAuthConfiguration,
IExternalAuthManager externalAuthManager,
UserRegistrationManager userRegistrationManager)
{
_logInManager = logInManager;
_tenantCache = tenantCache;
_abpLoginResultTypeHelper = abpLoginResultTypeHelper;
_configuration = configuration;
_externalAuthConfiguration = externalAuthConfiguration;
_externalAuthManager = externalAuthManager;
_userRegistrationManager = userRegistrationManager;
_signInManager = signInManager;
}
***[HttpPost]
public virtual async Task<JsonResult> Login(string UserName, string password,bool IsPersistent )
{
var loginResult = await GetLoginResultAsync(UserName, password, GetTenancyNameOrNull());
//var result = await _signInManager.SignInAsync(loginResult.Identity, IsPersistent);
var result = await _signInManager.PasswordSignInAsync(UserName, password, true, false);
if (result.Succeeded)
{
long bak= User.Identity.GetUserId().Value;
string res = "User signed in";
}
await UnitOfWorkManager.Current.SaveChangesAsync();
bool chk = User.Identity.IsAuthenticated;
return Json(new Abp.Web.Models.AjaxResponse { TargetUrl = "" });
}***
[HttpPost]
public async Task<AuthenticateResultModel> Authenticate([FromBody] AuthenticateModel model)
{
var loginResult = await GetLoginResultAsync(
model.UserNameOrEmailAddress,
model.Password,
GetTenancyNameOrNull()
);
//var chk = _logInManager.LoginAsync("jimycarbonare#gmail.com", "123qwe", "TSEDergi").Result;
//var chk2 = _logInManager.Login("jimycarbonare#gmail.com", "123qwe", "TSEDergi");
//var name = User.Identity.Name;
//bool bak0 = User.IsInRole("admin");
//var accessToken = CreateAccessToken(CreateJwtClaims(loginResult.Identity));
//var loginResult = await GetLoginResultAsync("jimycarbonare#gmail.com", "123qwe", "TSEDergi");
//await _signInManager.SignInAsync(loginResult.Identity, model.RememberClient);//_logInManager.LoginAsync("jimycarbonare#gmail.com", "123qwe", "TSEDergi").Result;
//var name = User.Identity.Name;
//bool bak0 = User.IsInRole("admin");
var accessToken = CreateAccessToken(CreateJwtClaims(loginResult.Identity));
return new AuthenticateResultModel
{
AccessToken = accessToken,
EncryptedAccessToken = GetEncrpyedAccessToken(accessToken),
ExpireInSeconds = (int)_configuration.Expiration.TotalSeconds,
UserId = loginResult.User.Id
};
}
[HttpGet]
public List<ExternalLoginProviderInfoModel> GetExternalAuthenticationProviders()
{
return ObjectMapper.Map<List<ExternalLoginProviderInfoModel>>(_externalAuthConfiguration.Providers);
}
[HttpPost]
public async Task<ExternalAuthenticateResultModel> ExternalAuthenticate([FromBody] ExternalAuthenticateModel model)
{
var externalUser = await GetExternalUserInfo(model);
var loginResult = await _logInManager.LoginAsync(new UserLoginInfo(model.AuthProvider, model.ProviderKey, model.AuthProvider), GetTenancyNameOrNull());
switch (loginResult.Result)
{
case AbpLoginResultType.Success:
{
var accessToken = CreateAccessToken(CreateJwtClaims(loginResult.Identity));
return new ExternalAuthenticateResultModel
{
AccessToken = accessToken,
EncryptedAccessToken = GetEncrpyedAccessToken(accessToken),
ExpireInSeconds = (int)_configuration.Expiration.TotalSeconds
};
}
case AbpLoginResultType.UnknownExternalLogin:
{
var newUser = await RegisterExternalUserAsync(externalUser);
if (!newUser.IsActive)
{
return new ExternalAuthenticateResultModel
{
WaitingForActivation = true
};
}
// Try to login again with newly registered user!
loginResult = await _logInManager.LoginAsync(new UserLoginInfo(model.AuthProvider, model.ProviderKey, model.AuthProvider), GetTenancyNameOrNull());
if (loginResult.Result != AbpLoginResultType.Success)
{
throw _abpLoginResultTypeHelper.CreateExceptionForFailedLoginAttempt(
loginResult.Result,
model.ProviderKey,
GetTenancyNameOrNull()
);
}
return new ExternalAuthenticateResultModel
{
AccessToken = CreateAccessToken(CreateJwtClaims(loginResult.Identity)),
ExpireInSeconds = (int)_configuration.Expiration.TotalSeconds
};
}
default:
{
throw _abpLoginResultTypeHelper.CreateExceptionForFailedLoginAttempt(
loginResult.Result,
model.ProviderKey,
GetTenancyNameOrNull()
);
}
}
}
private async Task<User> RegisterExternalUserAsync(ExternalAuthUserInfo externalUser)
{
var user = await _userRegistrationManager.RegisterAsync(
externalUser.Name,
externalUser.Surname,
externalUser.EmailAddress,
externalUser.EmailAddress,
Authorization.Users.User.CreateRandomPassword(),
true
);
user.Logins = new List<UserLogin>
{
new UserLogin
{
LoginProvider = externalUser.Provider,
ProviderKey = externalUser.ProviderKey,
TenantId = user.TenantId
}
};
await CurrentUnitOfWork.SaveChangesAsync();
return user;
}
private async Task<ExternalAuthUserInfo> GetExternalUserInfo(ExternalAuthenticateModel model)
{
var userInfo = await _externalAuthManager.GetUserInfo(model.AuthProvider, model.ProviderAccessCode);
if (userInfo.ProviderKey != model.ProviderKey)
{
throw new UserFriendlyException(L("CouldNotValidateExternalUser"));
}
return userInfo;
}
private string GetTenancyNameOrNull()
{
if (!AbpSession.TenantId.HasValue)
{
return null;
}
return _tenantCache.GetOrNull(AbpSession.TenantId.Value)?.TenancyName;
}
[HttpPost]
public AbpLoginResult<Tenant, User> GetLoginResult2Async(string usernameOrEmailAddress, string password, string tenancyName)
{
var loginResult = _logInManager.LoginAsync(usernameOrEmailAddress, password, tenancyName).Result;
switch (loginResult.Result)
{
case AbpLoginResultType.Success:
return loginResult;
default:
throw _abpLoginResultTypeHelper.CreateExceptionForFailedLoginAttempt(loginResult.Result, usernameOrEmailAddress, tenancyName);
}
}
private async Task<AbpLoginResult<Tenant, User>> GetLoginResultAsync(string usernameOrEmailAddress, string password, string tenancyName)
{
var loginResult = await _logInManager.LoginAsync(usernameOrEmailAddress, password, tenancyName);
switch (loginResult.Result)
{
case AbpLoginResultType.Success:
return loginResult;
default:
throw _abpLoginResultTypeHelper.CreateExceptionForFailedLoginAttempt(loginResult.Result, usernameOrEmailAddress, tenancyName);
}
}
private string CreateAccessToken(IEnumerable<Claim> claims, TimeSpan? expiration = null)
{
var now = DateTime.UtcNow;
var jwtSecurityToken = new JwtSecurityToken(
issuer: _configuration.Issuer,
audience: _configuration.Audience,
claims: claims,
notBefore: now,
expires: now.Add(expiration ?? _configuration.Expiration),
signingCredentials: _configuration.SigningCredentials
);
return new JwtSecurityTokenHandler().WriteToken(jwtSecurityToken);
}
private static List<Claim> CreateJwtClaims(ClaimsIdentity identity)
{
var claims = identity.Claims.ToList();
var nameIdClaim = claims.First(c => c.Type == ClaimTypes.NameIdentifier);
// Specifically add the jti (random nonce), iat (issued timestamp), and sub (subject/user) claims.
claims.AddRange(new[]
{
new Claim(JwtRegisteredClaimNames.Sub, nameIdClaim.Value),
new Claim(JwtRegisteredClaimNames.Jti, Guid.NewGuid().ToString()),
new Claim(JwtRegisteredClaimNames.Iat, DateTimeOffset.Now.ToUnixTimeSeconds().ToString(), ClaimValueTypes.Integer64)
});
return claims;
}
private string GetEncrpyedAccessToken(string accessToken)
{
return SimpleStringCipher.Instance.Encrypt(accessToken, AppConsts.DefaultPassPhrase);
}
}
}
I am getting a reasonable loginResult. And PasswordSignInAsync method returns with success. At that point I conclude the sign in process is OK. But after when I check User.Identity. I see it is null. Same is valid for the SignInAsync method.All I wanna do is sign-in the user only using the remote services. Thank you all..
I solved the problem as given below:
Change the httpost login method in AccountController of Web.Mvc application as below
[HttpPost]
[UnitOfWork]
public virtual async Task<JsonResult> Login(LoginViewModel loginModel, string returnUrl = "", string returnUrlHash = "")
{
var claims = GetClaims(loginModel.UsernameOrEmailAddress, loginModel.Password);
if (claims == null)//giriş yapılamadı
{
return Json(new AjaxResponse { TargetUrl = "" });
}
else
{
var identity = new ClaimsIdentity(CookieAuthenticationDefaults.AuthenticationScheme, ClaimTypes.Name,
ClaimTypes.Role);
foreach (var claim in claims)
{
identity.AddClaim(new Claim(claim.type, claim.value));
}
//AbpSession.UserId=18;
//// Authenticate using the identity
//var principal = new ClaimsPrincipal(identity);
//await HttpContext.SignInAsync(CookieAuthenticationDefaults.AuthenticationScheme, principal, new AuthenticationProperties { IsPersistent = true });
//bool chk = User.Identity.IsAuthenticated;
////bool bak = User.Identity.IsAuthenticated;
//bool bak2 = User.IsInRole("Admin");
//return RedirectToAction("Index", "Home");
await _signInManager.SignInAsync(identity, loginModel.RememberMe);
await UnitOfWorkManager.Current.SaveChangesAsync();
bool bak = User.Identity.IsAuthenticated;
var bakl = AbpSession.UserId;
}
returnUrl = NormalizeReturnUrl(returnUrl);
if (!string.IsNullOrWhiteSpace(returnUrlHash))
{
returnUrl = returnUrl + returnUrlHash;
}
return Json(new AjaxResponse { TargetUrl = returnUrl });
}
Create GetClaims method in AccountController of Web.Mvc application
protected List<ClaimRootObject> GetClaims(string UserName, string Password)
{
using (var client = new HttpClient())
{
string reqString = "http://localhost:21021/api/" + "TokenAuth/GetClaims/GetClaims?UserName=" + UserName + "&password=" + Password + "&TenantName=Default";
//string reqString = "http://localhost:81/api/TokenAuth/GetClaims/GetClaims?UserName=admin&password=123qwe&TenantName=TSEDergi";
HttpResponseMessage response = client.GetAsync(reqString).Result; // Blocking call!
if (response.IsSuccessStatusCode)
{
// Get the response
var JsonString = response.Content.ReadAsStringAsync();
// Deserialise the data (include the Newtonsoft JSON Nuget package if you don't already have it)
//List<Claim> deserialized = JsonConvert.DeserializeObject<List<Claim>>(JsonString.Result);
List<ClaimRootObject> deserialized = JsonConvert.DeserializeObject<List<ClaimRootObject>>(JsonString.Result);
if (deserialized != null)
{
return deserialized;
}
}
else
{
}
}
return null;
}
Create the required objects
public class ClaimRootObject
{
public string issuer { get; set; }
public string originalIssuer { get; set; }
public Properties properties { get; set; }
public Subject subject { get; set; }
public string type { get; set; }
public string value { get; set; }
public string valueType { get; set; }
}
public class Properties
{
}
public class Subject
{
public string authenticationType { get; set; }
public bool isAuthenticated { get; set; }
public object actor { get; set; }
public object bootstrapContext { get; set; }
public List claims { get; set; }
public object label { get; set; }
public string name { get; set; }
public string nameClaimType { get; set; }
public string roleClaimType { get; set; }
}
And last step, modify your startup class of Web.Mvc project to enable cookie authentication.
public IServiceProvider ConfigureServices(IServiceCollection services)
{
// MVC
services.AddMvc(
options => options.Filters.Add(new AutoValidateAntiforgeryTokenAttribute())
);
#region cookieAuthentication
services.AddAuthentication(CookieAuthenticationDefaults.AuthenticationScheme).AddCookie();
#endregion cookieAuthentication
IdentityRegistrar.Register(services);
AuthConfigurer.Configure(services, _appConfiguration);
services.AddScoped();
services.AddSignalR();
// Configure Abp and Dependency Injection
return services.AddAbp(
// Configure Log4Net logging
options => options.IocManager.IocContainer.AddFacility(
f => f.UseAbpLog4Net().WithConfig("log4net.config")
)
);
}
That's all. Then you can log in a user into the application using only remote services..

How to get associated name of linked resources in HAL

I am creating an API for project tasks. It has a TasksController as listed below. I am generating hypermedia using WebApi.Hal and the service supports hal+json and hal+xml media types also.
Following is the response I currently have for the GET request http://localhost:51910/api/tasks/1. In the response there is a list of links for priorities – but they don’t have associated name in the response (to show in the UI – like Low, Medium, High, etc.).
What is the best HAL approach for getting name of the priorities also, using WebApi.HAL?
Note: The list of priorities can be enhanced in the future.
Priority
public class Priority
{
public int PriorityID { get; set; }
public string PriorityName { get; set; }
public string Revision { get; set; }
public DateTime ApprovalDate { get; set; }
}
Controller
public class TasksController : ApiController
{
// GET api/values/5
[HttpGet]
public TaskRepresentation Get(int id)
{
Task selectedTask = TasksHelper.GetTask(id);
TaskRepresentation taskRepresentation = new TaskRepresentation(selectedTask);
return taskRepresentation;
}
//PUT For Setting Priority
[HttpPut]
[Route("api/tasks/{taskID}/priorities/{priorityID}")]
public TaskRepresentation PutSetPriority(int taskID, int priorityID)
{
Task selectedTask = TasksHelper.GetTask(taskID);
Priority selectedPriority = null;
List<Priority> allPriorities = TasksPrioritiesHelper.GetAllPriorities();
foreach (Priority p in allPriorities)
{
if (p.PriorityID == priorityID)
{
selectedPriority = p;
}
}
//Update Task
if (selectedPriority != null)
{
selectedTask.CurrentPriority = selectedPriority.PriorityName;
}
else
{
throw new Exception("Not available");
}
TaskRepresentation taskRepresentation = new TaskRepresentation(selectedTask);
return taskRepresentation;
}
[HttpGet]
[Route("api/tasks/{taskID}/priorities/{priorityID}")]
public Priority Get(int taskID, int priorityID)
{
Priority selectedPriority = null;
List<Priority> allPriorities = TasksPrioritiesHelper.GetAllPriorities();
foreach (Priority p in allPriorities)
{
if (p.PriorityID == priorityID)
{
selectedPriority = p;
}
}
return selectedPriority;
}
}
HAL Generation Related Classes
public static class LinkTemplates
{
public static class TaskLinks
{
public static Link TaskEntry { get { return new Link("self", "~/api/tasks/{taskID}"); } }
public static Link PriorityLink { get { return new Link("priorities", "~/api/tasks/{taskID}/priorities/{priorityID}"); } }
}
}
public class TaskRepresentation : Representation
{
Task theTask;
public int TaskID{get{return theTask.TaskID;}}
public string TaskName{get{return theTask.Name;}}
public string CurrentPriority{get{return theTask.CurrentPriority;}}
public string Category{get{return theTask.Category;}}
public TaskRepresentation(Task t)
{
theTask = t;
}
public override string Rel
{
get { return LinkTemplates.TaskLinks.TaskEntry.Rel; }
set { }
}
public override string Href
{
get { return LinkTemplates.TaskLinks.TaskEntry.CreateLink(new { taskID = theTask.TaskID }).Href; }
set { }
}
protected override void CreateHypermedia()
{
foreach (Priority p in theTask.PossiblePriorities)
{
Links.Add(LinkTemplates.TaskLinks.PriorityLink.CreateLink(new { taskID = theTask.TaskID, priorityID = p.PriorityID }));
}
}
}
HAL Specification mentions title - this will meet the requirement.
Following is the updated response.
{
"TaskID": 1,
"TaskName": "Task1",
"CurrentPriority": "Medium",
"Category": "IT",
"_links": {
"self": {
"href": "/api/tasks/1"
},
"priorities": [
{
"href": "/api/tasks/1/priorities/101",
"title": "Low"
},
{
"href": "/api/tasks/1/priorities/103",
"title": "High"
},
{
"href": "/api/tasks/1/priorities/104",
"title": "Critical"
}
]
}
}
WebAPI.HAL changes
protected override void CreateHypermedia()
{
foreach (Priority p in theTask.PossiblePriorities)
{
Link lnk = LinkTemplates.TaskLinks.PriorityLink.CreateLink(new { taskID = theTask.TaskID, priorityID = p.PriorityID });
lnk.Title = p.PriorityName;
Links.Add(lnk);
}
}
Code
public static class LinkTemplates
{
public static class TaskLinks
{
public static Link TaskEntry { get { return new Link("self", "~/api/tasks/{taskID}"); } }
//public static Link PriorityLink { get { return new Link("priorities", "~/api/tasks/{taskID}/priorities/{priorityID}"); } }
public static Link PriorityLink
{
get
{
Link l = new Link("priorities", "~/api/tasks/{taskID}/priorities/{priorityID}");
return l;
}
}
}
}
public class TasksController : ApiController
{
// GET api/values/5
[HttpGet]
public TaskRepresentation Get(int id)
{
Task selectedTask = TasksHelper.GetTask(id);
TaskRepresentation taskRepresentation = new TaskRepresentation(selectedTask);
return taskRepresentation;
}
//PUT For Setting Priority
[HttpPut]
[Route("api/tasks/{taskID}/priorities/{priorityID}")]
public TaskRepresentation PutSetPriority(int taskID, int priorityID)
{
Task selectedTask = TasksHelper.GetTask(taskID);
Priority selectedPriority = null;
List<Priority> allPriorities = TasksPrioritiesHelper.GetAllPriorities();
foreach (Priority p in allPriorities)
{
if (p.PriorityID == priorityID)
{
selectedPriority = p;
}
}
//Update Task
if (selectedPriority != null)
{
selectedTask.CurrentPriority = selectedPriority.PriorityName;
}
else
{
throw new Exception("Not available");
}
TaskRepresentation taskRepresentation = new TaskRepresentation(selectedTask);
return taskRepresentation;
}
[HttpGet]
[Route("api/tasks/{taskID}/priorities/{priorityID}")]
public Priority Get(int taskID, int priorityID)
{
Priority selectedPriority = null;
List<Priority> allPriorities = TasksPrioritiesHelper.GetAllPriorities();
foreach (Priority p in allPriorities)
{
if (p.PriorityID == priorityID)
{
selectedPriority = p;
}
}
return selectedPriority;
}
}
public class TaskRepresentation : Representation
{
Task theTask;
public int TaskID{get{return theTask.TaskID;}}
public string TaskName{get{return theTask.Name;}}
public string CurrentPriority{get{return theTask.CurrentPriority;}}
public string Category{get{return theTask.Category;}}
public TaskRepresentation(Task t)
{
theTask = t;
}
public override string Rel
{
get { return LinkTemplates.TaskLinks.TaskEntry.Rel; }
set { }
}
public override string Href
{
get { return LinkTemplates.TaskLinks.TaskEntry.CreateLink(new { taskID = theTask.TaskID }).Href; }
set { }
}
protected override void CreateHypermedia()
{
foreach (Priority p in theTask.PossiblePriorities)
{
Link lnk = LinkTemplates.TaskLinks.PriorityLink.CreateLink(new { taskID = theTask.TaskID, priorityID = p.PriorityID });
lnk.Title = p.PriorityName;
Links.Add(lnk);
}
}
}
public class Task
{
public string Name { get; set; }
public int TaskID { get; set; }
public string Category { get; set; }
public string CurrentPriority { get; set; }
public List<Priority> PossiblePriorities { get; set; }
}
public class Priority
{
public int PriorityID { get; set; }
public string PriorityName { get; set; }
public string Revision { get; set; }
public DateTime ApprovalDate { get; set; }
}
public static class TasksPrioritiesHelper
{
public static List<Priority> GetAllPriorities()
{
List<Priority> possiblePriorities = new List<Priority>();
Priority pLow = new Priority { PriorityID = 101, PriorityName = "Low" };
Priority pMedium = new Priority { PriorityID = 102, PriorityName = "Medium" };
Priority pHigh = new Priority { PriorityID = 103, PriorityName = "High" };
Priority pCritical = new Priority { PriorityID = 104, PriorityName = "Critical" };
possiblePriorities.Add(pLow);
possiblePriorities.Add(pMedium);
possiblePriorities.Add(pHigh);
possiblePriorities.Add(pCritical);
return possiblePriorities;
}
public static List<Priority> GetAdministrativePriorities()
{
List<Priority> possiblePriorities = new List<Priority>();
Priority pLow = new Priority { PriorityID = 101, PriorityName = "Low" };
Priority pHigh = new Priority { PriorityID = 103, PriorityName = "High" };
possiblePriorities.Add(pLow);
possiblePriorities.Add(pHigh);
return possiblePriorities;
}
public static List<Priority> GetPossiblePrioritiesForTask(Task t)
{
List<Priority> possibleTaskPriorities = new List<Priority>();
if (String.Equals(t.Category, "IT"))
{
possibleTaskPriorities = GetAllPriorities();
}
else
{
possibleTaskPriorities = GetAdministrativePriorities();
}
Priority currentTaskPriority = null;
foreach(Priority p in possibleTaskPriorities )
{
if(String.Equals(t.CurrentPriority,p.PriorityName ))
{
currentTaskPriority=p;
}
}
if(currentTaskPriority!=null)
{
possibleTaskPriorities.Remove(currentTaskPriority);
}
return possibleTaskPriorities;
}
}
public static class TasksHelper
{
public static Task GetTask(int id)
{
Task selectedTask = null;
List<Task> tasks = GetAllTasks();
foreach (Task t in tasks)
{
if(t.TaskID==id)
{
selectedTask = t;
}
}
return selectedTask;
}
public static List<Task> GetAllTasks()
{
List<Task> tasks;
tasks = new List<Task>();
Task t1 = new Task{Category = "IT",CurrentPriority = "Medium",Name = "Task1",TaskID = 1};
t1.PossiblePriorities = TasksPrioritiesHelper.GetPossiblePrioritiesForTask(t1);
tasks.Add(t1);
return tasks;
}
}

How to send complex objects in GET to WEB API 2

Let's say that you have the following code
public class MyClass {
public double Latitude {get; set;}
public double Longitude {get; set;}
}
public class Criteria
{
public DateTime StartDate { get; set; }
public DateTime EndDate { get; set; }
public MyClass MyProp {get; set;}
}
[HttpGet]
public Criteria Get([FromUri] Criteria c)
{
return c;
}
I'd like to know if someone is aware of a library that could transform any object into query string that is understood by a WEB API 2 Controller.
Here is an example of what I'd like
SerializeToQueryString(new Criteria{StartDate=DateTime.Today, EndDate = DateTime.Today.AddDays(1), MyProp = new MyProp{Latitude=1, Longitude=3}});
=> "startDate=2015-10-13&endDate=2015-10-14&myProp.latitude=1&myProp.longitude=3"
A full example with httpClient might look like :
new HttpClient("http://localhost").GetAsync("/tmp?"+SerializeToQueryString(new Criteria{StartDate=DateTime.Today, EndDate = DateTime.Today.AddDays(1), MyProp = new MyProp{Latitude=1, Longitude=3}})).Result;
At the moment, I use a version (taken from a question I do not find again, maybe How do I serialize an object into query-string format? ...).
The problem is that it is not working for anything else than simple properties.
For example, calling ToString on a Date will not give something that is parseable by WEB API 2 controller...
private string SerializeToQueryString<T>(T aObject)
{
var query = HttpUtility.ParseQueryString(string.Empty);
var fields = typeof(T).GetProperties();
foreach (var field in fields)
{
string key = field.Name;
var value = field.GetValue(aObject);
if (value != null)
query[key] = value.ToString();
}
return query.ToString();
}
"Transform any object to a query string" seems to imply there's a standard format for this, and there just isn't. So you would need to pick one or roll your own. JSON seems like the obvious choice due to the availability of great libraries.
Since it seems no one has dealt with the problem before, here is the solution I use in my project :
using System;
using System.Collections;
using System.Collections.Specialized;
using System.Globalization;
using System.Linq;
using System.Web;
namespace App
{
public class QueryStringSerializer
{
public static string SerializeToQueryString(object aObject)
{
return SerializeToQueryString(aObject, "").ToString();
}
private static NameValueCollection SerializeToQueryString(object aObject, string prefix)
{
//!\ doing this to get back a HttpValueCollection which is an internal class
//we want a HttpValueCollection because toString on this class is what we want in the public method
//cf http://stackoverflow.com/a/17096289/1545567
var query = HttpUtility.ParseQueryString(String.Empty);
var fields = aObject.GetType().GetProperties();
foreach (var field in fields)
{
string key = string.IsNullOrEmpty(prefix) ? field.Name : prefix + "." + field.Name;
var value = field.GetValue(aObject);
if (value != null)
{
var propertyType = GetUnderlyingPropertyType(field.PropertyType);
if (IsSupportedType(propertyType))
{
query.Add(key, ToString(value));
}
else if (value is IEnumerable)
{
var enumerableValue = (IEnumerable) value;
foreach (var enumerableValueElement in enumerableValue)
{
if (IsSupportedType(GetUnderlyingPropertyType(enumerableValueElement.GetType())))
{
query.Add(key, ToString(enumerableValueElement));
}
else
{
//it seems that WEB API 2 Controllers are unable to deserialize collections of complex objects...
throw new Exception("can not use IEnumerable<T> where T is a class because it is not understood server side");
}
}
}
else
{
var subquery = SerializeToQueryString(value, key);
query.Add(subquery);
}
}
}
return query;
}
private static Type GetUnderlyingPropertyType(Type propType)
{
var nullablePropertyType = Nullable.GetUnderlyingType(propType);
return nullablePropertyType ?? propType;
}
private static bool IsSupportedType(Type propertyType)
{
return SUPPORTED_TYPES.Contains(propertyType) || propertyType.IsEnum;
}
private static readonly Type[] SUPPORTED_TYPES = new[]
{
typeof(DateTime),
typeof(string),
typeof(int),
typeof(long),
typeof(float),
typeof(double)
};
private static string ToString(object value)
{
if (value is DateTime)
{
var dateValue = (DateTime) value;
if (dateValue.Hour == 0 && dateValue.Minute == 0 && dateValue.Second == 0)
{
return dateValue.ToString("yyyy-MM-dd");
}
else
{
return dateValue.ToString("yyyy-MM-dd HH:mm:ss");
}
}
else if (value is float)
{
return ((float) value).ToString(CultureInfo.InvariantCulture);
}
else if (value is double)
{
return ((double)value).ToString(CultureInfo.InvariantCulture);
}
else /*int, long, string, ENUM*/
{
return value.ToString();
}
}
}
}
Here is the unit test to demonstrate :
using System;
using System.Collections.Generic;
using System.Globalization;
using Microsoft.VisualStudio.TestTools.UnitTesting;
namespace Framework.WebApi.Core.Tests
{
[TestClass]
public class QueryStringSerializerTest
{
public class EasyObject
{
public string MyString { get; set; }
public int? MyInt { get; set; }
public long? MyLong { get; set; }
public float? MyFloat { get; set; }
public double? MyDouble { get; set; }
}
[TestMethod]
public void TestEasyObject()
{
var queryString = QueryStringSerializer.SerializeToQueryString(new EasyObject(){MyString = "string", MyInt = 1, MyLong = 1L, MyFloat = 1.5F, MyDouble = 1.4});
Assert.IsTrue(queryString.Contains("MyString=string"));
Assert.IsTrue(queryString.Contains("MyInt=1"));
Assert.IsTrue(queryString.Contains("MyLong=1"));
Assert.IsTrue(queryString.Contains("MyFloat=1.5"));
Assert.IsTrue(queryString.Contains("MyDouble=1.4"));
}
[TestMethod]
public void TestEasyObjectNullable()
{
var queryString = QueryStringSerializer.SerializeToQueryString(new EasyObject() { });
Assert.IsTrue(queryString == "");
}
[TestMethod]
public void TestUrlEncoding()
{
var queryString = QueryStringSerializer.SerializeToQueryString(new EasyObject() { MyString = "&=/;+" });
Assert.IsTrue(queryString.Contains("MyString=%26%3d%2f%3b%2b"));
}
public class DateObject
{
public DateTime MyDate { get; set; }
}
[TestMethod]
public void TestDate()
{
var d = DateTime.ParseExact("2010-10-13", "yyyy-MM-dd", CultureInfo.InvariantCulture);
var queryString = QueryStringSerializer.SerializeToQueryString(new DateObject() { MyDate = d });
Assert.IsTrue(queryString.Contains("MyDate=2010-10-13"));
}
[TestMethod]
public void TestDateTime()
{
var d = DateTime.ParseExact("2010-10-13 20:00", "yyyy-MM-dd HH:mm", CultureInfo.InvariantCulture);
var queryString = QueryStringSerializer.SerializeToQueryString(new DateObject() { MyDate = d });
Assert.IsTrue(queryString.Contains("MyDate=2010-10-13+20%3a00%3a00"));
}
public class InnerComplexObject
{
public double Lat { get; set; }
public double Lon { get; set; }
}
public class ComplexObject
{
public InnerComplexObject Inner { get; set; }
}
[TestMethod]
public void TestComplexObject()
{
var queryString = QueryStringSerializer.SerializeToQueryString(new ComplexObject() { Inner = new InnerComplexObject() {Lat = 50, Lon = 2} });
Assert.IsTrue(queryString.Contains("Inner.Lat=50"));
Assert.IsTrue(queryString.Contains("Inner.Lon=2"));
}
public class EnumerableObject
{
public IEnumerable<int> InnerInts { get; set; }
}
[TestMethod]
public void TestEnumerableObject()
{
var queryString = QueryStringSerializer.SerializeToQueryString(new EnumerableObject() {
InnerInts = new[] { 1,2 }
});
Assert.IsTrue(queryString.Contains("InnerInts=1"));
Assert.IsTrue(queryString.Contains("InnerInts=2"));
}
public class ComplexEnumerableObject
{
public IEnumerable<InnerComplexObject> Inners { get; set; }
}
[TestMethod]
public void TestComplexEnumerableObject()
{
try
{
QueryStringSerializer.SerializeToQueryString(new ComplexEnumerableObject()
{
Inners = new[]
{
new InnerComplexObject() {Lat = 50, Lon = 2},
new InnerComplexObject() {Lat = 51, Lon = 3},
}
});
Assert.Fail("we should refuse something that will not be understand by the server");
}
catch (Exception e)
{
Assert.AreEqual("can not use IEnumerable<T> where T is a class because it is not understood server side", e.Message);
}
}
public enum TheEnum : int
{
One = 1,
Two = 2
}
public class EnumObject
{
public TheEnum? MyEnum { get; set; }
}
[TestMethod]
public void TestEnum()
{
var queryString = QueryStringSerializer.SerializeToQueryString(new EnumObject() { MyEnum = TheEnum.Two});
Assert.IsTrue(queryString.Contains("MyEnum=Two"));
}
}
}
I'd like to thank all the participants even if this is not something that you should usually do in a Q&A format :)

Resources