init
This commit is contained in:
@@ -0,0 +1,26 @@
|
||||
<Project Sdk="Microsoft.NET.Sdk.Web">
|
||||
|
||||
<PropertyGroup>
|
||||
<TargetFramework>net10.0</TargetFramework>
|
||||
<ImplicitUsings>enable</ImplicitUsings>
|
||||
<IsPackable>true</IsPackable>
|
||||
<GenerateDocumentationFile>true</GenerateDocumentationFile>
|
||||
<NoWarn>$(NoWarn);1591</NoWarn>
|
||||
</PropertyGroup>
|
||||
<ItemGroup>
|
||||
<PackageReference Include="Microsoft.EntityFrameworkCore.Design">
|
||||
<PrivateAssets>all</PrivateAssets>
|
||||
<IncludeAssets>runtime; build; native; contentfiles; analyzers; buildtransitive</IncludeAssets>
|
||||
</PackageReference>
|
||||
<PackageReference Include="Serilog.AspNetCore" />
|
||||
<PackageReference Include="Microsoft.AspNetCore.OpenApi" />
|
||||
<PackageReference Include="Microsoft.Extensions.Logging.Debug" />
|
||||
</ItemGroup>
|
||||
|
||||
<ItemGroup>
|
||||
<ProjectReference Include="..\..\Infrastructure\CleanArc.Infrastructure.Monitoring\CleanArc.Infrastructure.Monitoring.csproj" />
|
||||
<ProjectReference Include="..\CleanArc.WebFramework\CleanArc.WebFramework.csproj" />
|
||||
<ProjectReference Include="..\Plugins\CleanArc.Web.Plugins.Grpc\CleanArc.Web.Plugins.Grpc.csproj" />
|
||||
</ItemGroup>
|
||||
|
||||
</Project>
|
||||
@@ -0,0 +1,37 @@
|
||||
using Asp.Versioning;
|
||||
using CleanArc.Application.Features.Admin.Commands.AddAdminCommand;
|
||||
using CleanArc.Application.Features.Admin.Queries.GetToken;
|
||||
using CleanArc.Application.Models.Jwt;
|
||||
using CleanArc.WebFramework.Attributes;
|
||||
using CleanArc.WebFramework.BaseController;
|
||||
using Mediator;
|
||||
using Microsoft.AspNetCore.Authorization;
|
||||
using Microsoft.AspNetCore.Mvc;
|
||||
|
||||
namespace CleanArc.Web.Api.Controllers.V1.Admin
|
||||
{
|
||||
[ApiVersion("1")]
|
||||
[ApiController]
|
||||
[Route("api/v{version:apiVersion}/AdminManager")]
|
||||
public class AdminManagerController(ISender sender) : BaseController
|
||||
{
|
||||
[HttpPost("Login")]
|
||||
[ProducesOkApiResponseType<AccessToken>]
|
||||
public async Task<IActionResult> AdminLogin(AdminGetTokenQuery model)
|
||||
{
|
||||
var query = await sender.Send(model);
|
||||
|
||||
return base.OperationResult(query);
|
||||
}
|
||||
|
||||
[Authorize(Roles = "admin")]
|
||||
[HttpPost("NewAdmin")]
|
||||
[ProducesOkApiResponseType]
|
||||
public async Task<IActionResult> AddNewAdmin(AddAdminCommand model)
|
||||
{
|
||||
var commandResult = await sender.Send(model);
|
||||
|
||||
return base.OperationResult(commandResult);
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,36 @@
|
||||
using CleanArc.Infrastructure.Identity.Identity.PermissionManager;
|
||||
using Microsoft.AspNetCore.Authorization;
|
||||
using Microsoft.AspNetCore.Mvc;
|
||||
using System.ComponentModel.DataAnnotations;
|
||||
using Asp.Versioning;
|
||||
using CleanArc.Application.Features.Order.Queries.GetAllOrders;
|
||||
using CleanArc.WebFramework.Attributes;
|
||||
using CleanArc.WebFramework.BaseController;
|
||||
using Mediator;
|
||||
|
||||
namespace CleanArc.Web.Api.Controllers.V1.Admin
|
||||
{
|
||||
[ApiVersion("1")]
|
||||
[ApiController]
|
||||
[Route("api/v{version:apiVersion}/OrderManagement")]
|
||||
[Display(Description= "Managing Users related Orders")]
|
||||
[Authorize(ConstantPolicies.DynamicPermission)]
|
||||
public class OrderManagementController : BaseController
|
||||
{
|
||||
private readonly ISender _sender;
|
||||
|
||||
public OrderManagementController(ISender sender)
|
||||
{
|
||||
_sender = sender;
|
||||
}
|
||||
|
||||
[HttpGet("OrderList")]
|
||||
[ProducesOkApiResponseType<List<GetAllOrdersQueryResult>>]
|
||||
public async Task<IActionResult> GetOrders()
|
||||
{
|
||||
var queryResult = await _sender.Send(new GetAllOrdersQuery());
|
||||
|
||||
return base.OperationResult(queryResult);
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,67 @@
|
||||
using System.ComponentModel.DataAnnotations;
|
||||
using Asp.Versioning;
|
||||
using CleanArc.Application.Features.Role.Commands.AddRoleCommand;
|
||||
using CleanArc.Application.Features.Role.Commands.UpdateRoleClaimsCommand;
|
||||
using CleanArc.Application.Features.Role.Queries.GetAllRolesQuery;
|
||||
using CleanArc.Application.Features.Role.Queries.GetAuthorizableRoutesQuery;
|
||||
using CleanArc.Infrastructure.Identity.Identity.PermissionManager;
|
||||
using CleanArc.WebFramework.Attributes;
|
||||
using CleanArc.WebFramework.BaseController;
|
||||
using Mediator;
|
||||
using Microsoft.AspNetCore.Authorization;
|
||||
using Microsoft.AspNetCore.Mvc;
|
||||
|
||||
namespace CleanArc.Web.Api.Controllers.V1.Admin
|
||||
{
|
||||
[ApiVersion("1")]
|
||||
[ApiController]
|
||||
[Route("api/v{version:apiVersion}/RoleManager")]
|
||||
[Authorize(ConstantPolicies.DynamicPermission)]
|
||||
[Display(Description = "Managing Related Roles for the System")]
|
||||
|
||||
public class RoleManagerController(ISender sender) : BaseController
|
||||
{
|
||||
[HttpGet("Roles")]
|
||||
[ProducesOkApiResponseType<List<GetAllRolesQueryResponse>>]
|
||||
public async Task<IActionResult> GetRoles()
|
||||
{
|
||||
var queryResult = await sender.Send(new GetAllRolesQuery());
|
||||
|
||||
return base.OperationResult(queryResult);
|
||||
}
|
||||
|
||||
[HttpGet("AuthRoutes")]
|
||||
[ProducesOkApiResponseType<List<GetAuthorizableRoutesQueryResponse>>]
|
||||
public async Task<IActionResult> GetAuthRoutes()
|
||||
{
|
||||
var queryModel = await sender.Send(new GetAuthorizableRoutesQuery());
|
||||
|
||||
return base.OperationResult(queryModel);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Update a role permissions (claims) based on RouteKey received in AuthRoutes API
|
||||
/// </summary>
|
||||
/// <param name="model"></param>
|
||||
/// <returns></returns>
|
||||
[HttpPut("UpdateRolePermissions")]
|
||||
[ProducesOkApiResponseType]
|
||||
public async Task<IActionResult> UpdateRolePermissions(UpdateRoleClaimsCommand model)
|
||||
{
|
||||
var commandResult =
|
||||
await sender.Send(new UpdateRoleClaimsCommand(model.RoleId, model.RoleClaimValue));
|
||||
|
||||
return base.OperationResult(commandResult);
|
||||
}
|
||||
|
||||
[HttpPost("NewRole")]
|
||||
[ProducesOkApiResponseType]
|
||||
public async Task<IActionResult> AddRole(AddRoleCommand model)
|
||||
{
|
||||
var commandResult = await sender.Send(model);
|
||||
|
||||
return base.OperationResult(commandResult);
|
||||
}
|
||||
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,36 @@
|
||||
using CleanArc.Infrastructure.Identity.Identity.PermissionManager;
|
||||
using Microsoft.AspNetCore.Authorization;
|
||||
using Microsoft.AspNetCore.Mvc;
|
||||
using System.ComponentModel.DataAnnotations;
|
||||
using Asp.Versioning;
|
||||
using CleanArc.Application.Features.Users.Queries.GetUsers;
|
||||
using CleanArc.WebFramework.Attributes;
|
||||
using CleanArc.WebFramework.BaseController;
|
||||
using Mediator;
|
||||
|
||||
namespace CleanArc.Web.Api.Controllers.V1.Admin
|
||||
{
|
||||
[ApiVersion("1")]
|
||||
[ApiController]
|
||||
[Route("api/v{version:apiVersion}/UserManagement")]
|
||||
[Display(Description = "Managing API Users")]
|
||||
[Authorize(ConstantPolicies.DynamicPermission)]
|
||||
public class UserManagementController : BaseController
|
||||
{
|
||||
private readonly ISender _sender;
|
||||
|
||||
public UserManagementController(ISender sender)
|
||||
{
|
||||
_sender = sender;
|
||||
}
|
||||
|
||||
[HttpGet("CurrentUsers")]
|
||||
[ProducesOkApiResponseType<List<GetUsersQueryResponse>>]
|
||||
public async Task<IActionResult> GetAllUsers()
|
||||
{
|
||||
var queryResult = await _sender.Send(new GetUsersQuery());
|
||||
|
||||
return base.OperationResult(queryResult);
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,52 @@
|
||||
using Asp.Versioning;
|
||||
using CleanArc.Application.Features.Order.Commands;
|
||||
using CleanArc.Application.Features.Order.Queries.GetUserOrders;
|
||||
using CleanArc.WebFramework.Attributes;
|
||||
using CleanArc.WebFramework.BaseController;
|
||||
using Mediator;
|
||||
using Microsoft.AspNetCore.Authorization;
|
||||
using Microsoft.AspNetCore.Mvc;
|
||||
|
||||
namespace CleanArc.Web.Api.Controllers.V1.Order;
|
||||
|
||||
[ApiVersion("1")]
|
||||
[ApiController]
|
||||
[Route("api/v{version:apiVersion}/User")]
|
||||
[Authorize]
|
||||
public class OrderController(ISender sender) : BaseController
|
||||
{
|
||||
[HttpPost("CreateNewOrder")]
|
||||
[ProducesOkApiResponseType]
|
||||
public async Task<IActionResult> CreateNewOrder(AddOrderCommand model)
|
||||
{
|
||||
model.UserId = base.UserId;
|
||||
var command = await sender.Send(model);
|
||||
|
||||
return base.OperationResult(command);
|
||||
}
|
||||
|
||||
[HttpGet("GetUserOrders")]
|
||||
[ProducesOkApiResponseType<List<GetUsersQueryResultModel>>]
|
||||
public async Task<IActionResult> GetUserOrders()
|
||||
{
|
||||
var query = await sender.Send(new GetUserOrdersQueryModel(UserId));
|
||||
|
||||
return base.OperationResult(query);
|
||||
}
|
||||
|
||||
[HttpPut("UpdateOrder")]
|
||||
[ProducesOkApiResponseType]
|
||||
public async Task<IActionResult> UpdateOrder(UpdateUserOrderCommand model)
|
||||
{
|
||||
model.UserId=base.UserId;
|
||||
|
||||
var command = await sender.Send(model);
|
||||
|
||||
return base.OperationResult(command);
|
||||
}
|
||||
|
||||
[HttpDelete("DeleteAllUserOrders")]
|
||||
[ProducesOkApiResponseType]
|
||||
public async Task<IActionResult> DeleteAllUserOrders()
|
||||
=> base.OperationResult(await sender.Send(new DeleteUserOrdersCommand(base.UserId)));
|
||||
}
|
||||
@@ -0,0 +1,88 @@
|
||||
using Asp.Versioning;
|
||||
using CleanArc.Application.Features.Users.Commands.Create;
|
||||
using CleanArc.Application.Features.Users.Commands.RefreshUserTokenCommand;
|
||||
using CleanArc.Application.Features.Users.Commands.RequestLogout;
|
||||
using CleanArc.Application.Features.Users.Queries.GenerateUserToken;
|
||||
using CleanArc.Application.Features.Users.Queries.TokenRequest;
|
||||
using CleanArc.Application.Models.Jwt;
|
||||
using CleanArc.WebFramework.Attributes;
|
||||
using CleanArc.WebFramework.BaseController;
|
||||
using CleanArc.WebFramework.Swagger;
|
||||
using Mediator;
|
||||
using Microsoft.AspNetCore.Authentication;
|
||||
using Microsoft.AspNetCore.Authentication.JwtBearer;
|
||||
using Microsoft.AspNetCore.Authorization;
|
||||
using Microsoft.AspNetCore.Mvc;
|
||||
|
||||
namespace CleanArc.Web.Api.Controllers.V1.UserManagement;
|
||||
|
||||
[ApiVersion("1")]
|
||||
[ApiController]
|
||||
[Route("api/v{version:apiVersion}/User")]
|
||||
public class UserController : BaseController
|
||||
{
|
||||
private readonly IMediator _mediator;
|
||||
|
||||
public UserController(IMediator mediator)
|
||||
{
|
||||
_mediator = mediator;
|
||||
}
|
||||
|
||||
[HttpPost("Register")]
|
||||
[ProducesOkApiResponseType<UserCreateCommandResult>]
|
||||
public async Task<IActionResult> CreateUser(UserCreateCommand model)
|
||||
{
|
||||
var command = await _mediator.Send(model);
|
||||
|
||||
return base.OperationResult(command);
|
||||
}
|
||||
|
||||
|
||||
[HttpPost("TokenRequest")]
|
||||
[ProducesOkApiResponseType<UserTokenRequestQueryResponse>]
|
||||
public async Task<IActionResult> TokenRequest(UserTokenRequestQuery model)
|
||||
{
|
||||
var query = await _mediator.Send(model);
|
||||
|
||||
return base.OperationResult(query);
|
||||
}
|
||||
|
||||
[HttpPost("LoginConfirmation")]
|
||||
[ProducesOkApiResponseType<AccessToken>]
|
||||
public async Task<IActionResult> ValidateUser(GenerateUserTokenQuery model)
|
||||
{
|
||||
var result = await _mediator.Send(model);
|
||||
|
||||
return base.OperationResult(result);
|
||||
}
|
||||
|
||||
[HttpPost("RefreshSignIn")]
|
||||
[RequireTokenWithoutAuthorization]
|
||||
[ProducesOkApiResponseType<AccessToken>]
|
||||
public async Task<IActionResult> RefreshUserToken(RefreshUserTokenCommand model)
|
||||
{
|
||||
var checkCurrentAccessTokenValidity =await HttpContext.AuthenticateAsync(JwtBearerDefaults.AuthenticationScheme);
|
||||
|
||||
if (checkCurrentAccessTokenValidity.Succeeded)
|
||||
return BadRequest("Current access token is valid. No need to refresh");
|
||||
|
||||
var newTokenResult = await _mediator.Send(model);
|
||||
|
||||
return base.OperationResult(newTokenResult);
|
||||
}
|
||||
|
||||
[HttpPost("Logout")]
|
||||
[Authorize]
|
||||
[ProducesOkApiResponseType]
|
||||
public async Task<IActionResult> RequestLogout()
|
||||
{
|
||||
var commandResult = await _mediator.Send(new RequestLogoutCommand(base.UserId));
|
||||
|
||||
return base.OperationResult(commandResult);
|
||||
}
|
||||
|
||||
[HttpPost("PasswordTokenRequest")]
|
||||
[ProducesOkApiResponseType<AccessToken>]
|
||||
public async Task<IActionResult> PasswordTokenRequest(PasswordUserTokenRequestQuery model)
|
||||
=> base.OperationResult(await _mediator.Send(model));
|
||||
}
|
||||
@@ -0,0 +1,118 @@
|
||||
using System.Diagnostics;
|
||||
using CleanArc.Application.Features.Users.Commands.Create;
|
||||
using CleanArc.Application.Models.ApiResult;
|
||||
using CleanArc.Application.Models.Identity;
|
||||
using CleanArc.Application.ServiceConfiguration;
|
||||
using CleanArc.Domain.Entities.User;
|
||||
using CleanArc.Infrastructure.CrossCutting.Logging;
|
||||
using CleanArc.Infrastructure.Identity.Identity.Dtos;
|
||||
using CleanArc.Infrastructure.Identity.Jwt;
|
||||
using CleanArc.Infrastructure.Identity.ServiceConfiguration;
|
||||
using CleanArc.Infrastructure.Monitoring.Configurations;
|
||||
using CleanArc.Infrastructure.Persistence.ServiceConfiguration;
|
||||
using CleanArc.SharedKernel.Extensions;
|
||||
using CleanArc.Web.Api.Controllers.V1.UserManagement;
|
||||
using CleanArc.Web.Plugins.Grpc;
|
||||
using CleanArc.WebFramework.Filters;
|
||||
using CleanArc.WebFramework.Middlewares;
|
||||
using CleanArc.WebFramework.ServiceConfiguration;
|
||||
using CleanArc.WebFramework.Swagger;
|
||||
using Mapster;
|
||||
using Microsoft.AspNetCore.Mvc;
|
||||
using Serilog;
|
||||
|
||||
var builder = WebApplication.CreateBuilder(args);
|
||||
|
||||
builder.Host.UseSerilog(LoggingConfiguration.ConfigureLogger);
|
||||
|
||||
var configuration = builder.Configuration;
|
||||
|
||||
Activity.DefaultIdFormat = ActivityIdFormat.W3C;
|
||||
|
||||
builder
|
||||
.ConfigureHealthChecks()
|
||||
.SetupOpenTelemetry();
|
||||
|
||||
builder.Services.Configure<IdentitySettings>(configuration.GetSection(nameof(IdentitySettings)));
|
||||
|
||||
var identitySettings = configuration.GetSection(nameof(IdentitySettings)).Get<IdentitySettings>();
|
||||
|
||||
builder.Services.AddControllers(options =>
|
||||
{
|
||||
options.Filters.Add(typeof(OkResultAttribute));
|
||||
options.Filters.Add(typeof(NotFoundResultAttribute));
|
||||
options.Filters.Add(typeof(ContentResultFilterAttribute));
|
||||
options.Filters.Add(typeof(ModelStateValidationAttribute));
|
||||
options.Filters.Add(typeof(BadRequestResultFilterAttribute));
|
||||
options.Filters.Add(new ProducesResponseTypeAttribute(typeof(ApiResult<Dictionary<string, List<string>>>),
|
||||
StatusCodes.Status400BadRequest));
|
||||
options.Filters.Add(new ProducesResponseTypeAttribute(typeof(ApiResult),
|
||||
StatusCodes.Status401Unauthorized));
|
||||
options.Filters.Add(new ProducesResponseTypeAttribute(typeof(ApiResult),
|
||||
StatusCodes.Status403Forbidden));
|
||||
options.Filters.Add(new ProducesResponseTypeAttribute(typeof(ApiResult),
|
||||
StatusCodes.Status500InternalServerError));
|
||||
|
||||
}).ConfigureApiBehaviorOptions(options =>
|
||||
{
|
||||
options.SuppressModelStateInvalidFilter = true;
|
||||
options.SuppressMapClientErrors = true;
|
||||
});
|
||||
|
||||
|
||||
builder.Services.AddEndpointsApiExplorer();
|
||||
builder.Services.AddSwagger("v1","v1.1");
|
||||
|
||||
|
||||
builder.Services.AddApplicationServices()
|
||||
.RegisterIdentityServices(identitySettings)
|
||||
.AddPersistenceServices(configuration)
|
||||
.AddWebFrameworkServices();
|
||||
|
||||
builder.Services.RegisterValidatorsAsServices();
|
||||
builder.Services.AddExceptionHandler<ExceptionHandler>();
|
||||
|
||||
builder.Services.AddMapster();
|
||||
|
||||
TypeAdapterConfig.GlobalSettings.Scan(typeof(UserCreateCommand).Assembly,
|
||||
typeof(GetRolesDto).Assembly);
|
||||
|
||||
|
||||
#region Plugin Services Configuration
|
||||
|
||||
builder.Services.ConfigureGrpcPluginServices();
|
||||
|
||||
#endregion
|
||||
|
||||
|
||||
|
||||
var app = builder.Build();
|
||||
|
||||
|
||||
await app.ApplyMigrationsAsync();
|
||||
await app.SeedDefaultUsersAsync();
|
||||
|
||||
if (app.Environment.IsDevelopment())
|
||||
{
|
||||
app.UseDeveloperExceptionPage();
|
||||
}
|
||||
else
|
||||
app.UseExceptionHandler(_=>{});
|
||||
|
||||
app.UseSwaggerAndUi();
|
||||
|
||||
app.UseRouting();
|
||||
|
||||
app.UseAuthentication();
|
||||
app.UseAuthorization();
|
||||
app.MapControllers();
|
||||
|
||||
app.UseMetrics()
|
||||
.UseHealthChecks();
|
||||
|
||||
app.ConfigureGrpcPipeline();
|
||||
|
||||
await app.RunAsync();
|
||||
|
||||
|
||||
|
||||
@@ -0,0 +1,31 @@
|
||||
{
|
||||
"$schema": "http://json.schemastore.org/launchsettings.json",
|
||||
"iisSettings": {
|
||||
"windowsAuthentication": false,
|
||||
"anonymousAuthentication": true,
|
||||
"iisExpress": {
|
||||
"applicationUrl": "http://localhost:8746",
|
||||
"sslPort": 0
|
||||
}
|
||||
},
|
||||
"profiles": {
|
||||
"IIS Express": {
|
||||
"commandName": "IISExpress",
|
||||
"launchBrowser": true,
|
||||
"launchUrl": "swagger",
|
||||
"environmentVariables": {
|
||||
"ASPNETCORE_ENVIRONMENT": "Development"
|
||||
}
|
||||
},
|
||||
"Web.Api": {
|
||||
"commandName": "Project",
|
||||
"dotnetRunMessages": true,
|
||||
"launchBrowser": true,
|
||||
"launchUrl": "swagger",
|
||||
"applicationUrl": "https://localhost:5002",
|
||||
"environmentVariables": {
|
||||
"ASPNETCORE_ENVIRONMENT": "Development"
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,12 @@
|
||||
{
|
||||
"ConnectionStrings": {
|
||||
"SqlServer": "Data Source=localhost;Initial Catalog=CleanArc_DB_8_0;Integrated Security=true;Encrypt=False"
|
||||
},
|
||||
"Logging": {
|
||||
"LogLevel": {
|
||||
"Default": "Information",
|
||||
"Microsoft": "Warning",
|
||||
"Microsoft.Hosting.Lifetime": "Information"
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,20 @@
|
||||
{
|
||||
"ConnectionStrings": {
|
||||
"SqlServer": "Server=sql_server2022;Database=CleanArc_DB_Docker;User Id=SA;Password=A&VeryComplex123Password;MultipleActiveResultSets=true;encrypt=false",
|
||||
"logDb": "Server=sql_server2022;Database=CleanArc_Log_DB_Docker;User Id=SA;Password=A&VeryComplex123Password;MultipleActiveResultSets=true;encrypt=false"
|
||||
},
|
||||
"IdentitySettings": {
|
||||
"SecretKey": "ShouldBe-LongerThan-16Char-SecretKey",
|
||||
"Encryptkey": "16CharEncryptKey",
|
||||
"Issuer": "MyWebsite",
|
||||
"Audience": "MyWebsite",
|
||||
"NotBeforeMinutes": "0",
|
||||
"ExpirationMinutes": "10000"
|
||||
},
|
||||
"AllowedHosts": "*",
|
||||
"Kestrel": {
|
||||
"EndpointDefaults": {
|
||||
"Protocols": "Http2"
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,39 @@
|
||||
using CleanArc.Application.Models.ApiResult;
|
||||
using Microsoft.AspNetCore.Http;
|
||||
using Microsoft.AspNetCore.Mvc;
|
||||
|
||||
namespace CleanArc.WebFramework.Attributes;
|
||||
|
||||
/// <summary>
|
||||
/// Documents the 200 OK response type of endpoint as ApiResult
|
||||
/// </summary>
|
||||
/// <typeparam name="TResponse">API response type that will be in data JSON property</typeparam>
|
||||
public class ProducesOkApiResponseType<TResponse>:ProducesResponseTypeAttribute
|
||||
{
|
||||
private ProducesOkApiResponseType(int statusCode) : base(statusCode)
|
||||
{
|
||||
}
|
||||
|
||||
public ProducesOkApiResponseType() : base(typeof(ApiResult<TResponse>), StatusCodes.Status200OK)
|
||||
{
|
||||
}
|
||||
|
||||
private ProducesOkApiResponseType(Type type, int statusCode, string contentType, params string[] additionalContentTypes) : base(type, statusCode, contentType, additionalContentTypes)
|
||||
{
|
||||
}
|
||||
}
|
||||
|
||||
public class ProducesOkApiResponseType:ProducesResponseTypeAttribute
|
||||
{
|
||||
private ProducesOkApiResponseType(int statusCode) : base(statusCode)
|
||||
{
|
||||
}
|
||||
|
||||
public ProducesOkApiResponseType() : base(typeof(ApiResult), StatusCodes.Status200OK)
|
||||
{
|
||||
}
|
||||
|
||||
private ProducesOkApiResponseType(Type type, int statusCode, string contentType, params string[] additionalContentTypes) : base(type, statusCode, contentType, additionalContentTypes)
|
||||
{
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,52 @@
|
||||
using System.Security.Claims;
|
||||
using CleanArc.Application.Models.Common;
|
||||
using CleanArc.SharedKernel.Extensions;
|
||||
using CleanArc.WebFramework.Filters;
|
||||
using Microsoft.AspNetCore.Mvc;
|
||||
|
||||
namespace CleanArc.WebFramework.BaseController;
|
||||
|
||||
public class BaseController : ControllerBase
|
||||
{
|
||||
protected string UserName => User.Identity?.Name;
|
||||
protected int UserId => int.Parse(User.Identity.GetUserId());
|
||||
protected string UserEmail => User.Identity.FindFirstValue(ClaimTypes.Email);
|
||||
protected string UserRole => User.Identity.FindFirstValue(ClaimTypes.Role);
|
||||
|
||||
protected string UserKey => User.FindFirstValue(ClaimTypes.UserData);
|
||||
|
||||
protected IActionResult OperationResult<TModel>(OperationResult<TModel> result)
|
||||
{
|
||||
if (result is null)
|
||||
return new ServerErrorResult("Server Error");
|
||||
|
||||
|
||||
if (result.IsSuccess)
|
||||
return result.Result is bool ? Ok() : Ok(result.Result);
|
||||
|
||||
if (result.IsNotFound)
|
||||
{
|
||||
|
||||
AddErrors(result);
|
||||
|
||||
var notFoundErrors = new ValidationProblemDetails(ModelState);
|
||||
|
||||
return NotFound(notFoundErrors.Errors);
|
||||
}
|
||||
|
||||
AddErrors(result);
|
||||
|
||||
var badRequestErrors = new ValidationProblemDetails(ModelState);
|
||||
|
||||
return BadRequest(badRequestErrors.Errors);
|
||||
|
||||
}
|
||||
|
||||
private void AddErrors<TModel>(OperationResult<TModel> result)
|
||||
{
|
||||
foreach (var error in result.ErrorMessages)
|
||||
{
|
||||
ModelState.AddModelError(error.Key,error.Value);
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,23 @@
|
||||
<Project Sdk="Microsoft.NET.Sdk">
|
||||
|
||||
<PropertyGroup>
|
||||
<TargetFramework>net10.0</TargetFramework>
|
||||
<ImplicitUsings>enable</ImplicitUsings>
|
||||
</PropertyGroup>
|
||||
|
||||
<ItemGroup>
|
||||
<PackageReference Include="NSwag.AspNetCore" />
|
||||
<PackageReference Include="NuGet.Packaging" />
|
||||
<PackageReference Include="Asp.Versioning.Http" />
|
||||
<PackageReference Include="Asp.Versioning.Mvc" />
|
||||
<PackageReference Include="Asp.Versioning.Mvc.ApiExplorer" />
|
||||
</ItemGroup>
|
||||
|
||||
<ItemGroup>
|
||||
<ProjectReference Include="..\..\Core\CleanArc.Application\CleanArc.Application.csproj" />
|
||||
<ProjectReference Include="..\..\Infrastructure\CleanArc.Infrastructure.CrossCutting\CleanArc.Infrastructure.CrossCutting.csproj" />
|
||||
<ProjectReference Include="..\..\Infrastructure\CleanArc.Infrastructure.Identity\CleanArc.Infrastructure.Identity.csproj" />
|
||||
<ProjectReference Include="..\..\Infrastructure\CleanArc.Infrastructure.Persistence\CleanArc.Infrastructure.Persistence.csproj" />
|
||||
</ItemGroup>
|
||||
|
||||
</Project>
|
||||
+28
@@ -0,0 +1,28 @@
|
||||
using CleanArc.Application.Models.ApiResult;
|
||||
using Microsoft.AspNetCore.Http;
|
||||
|
||||
namespace CleanArc.WebFramework.EndpointFilters;
|
||||
|
||||
public class BadRequestResultEndpointFilter:IEndpointFilter
|
||||
{
|
||||
public async ValueTask<object> InvokeAsync(EndpointFilterInvocationContext context, EndpointFilterDelegate next)
|
||||
{
|
||||
var result = await next(context);
|
||||
|
||||
if (result is not IStatusCodeHttpResult statusCodeResult)
|
||||
{
|
||||
return result;
|
||||
}
|
||||
|
||||
if (statusCodeResult.StatusCode != StatusCodes.Status400BadRequest)
|
||||
return result;
|
||||
|
||||
|
||||
if (result is IValueHttpResult valueHttp)
|
||||
{
|
||||
return Results.BadRequest(new ApiResult<object>(false, ApiResultStatusCode.BadRequest, valueHttp.Value));
|
||||
}
|
||||
|
||||
return Results.BadRequest(new ApiResult(false, ApiResultStatusCode.BadRequest));
|
||||
}
|
||||
}
|
||||
+46
@@ -0,0 +1,46 @@
|
||||
using FluentValidation;
|
||||
using Microsoft.AspNetCore.Http;
|
||||
|
||||
namespace CleanArc.WebFramework.EndpointFilters;
|
||||
|
||||
public class ModelStateValidationEndpointFilter:IEndpointFilter
|
||||
{
|
||||
public async ValueTask<object> InvokeAsync(EndpointFilterInvocationContext context, EndpointFilterDelegate next)
|
||||
{
|
||||
|
||||
var validationSummery = new Dictionary<string, List<string>>();
|
||||
|
||||
foreach (var contextArgument in context.Arguments)
|
||||
{
|
||||
if (contextArgument is null)
|
||||
continue;
|
||||
|
||||
var validator =
|
||||
context.HttpContext.RequestServices.GetService(
|
||||
typeof(IValidator<>).MakeGenericType(contextArgument.GetType())) as IValidator;
|
||||
|
||||
if (validator is null)
|
||||
continue;
|
||||
|
||||
var validationResult = await validator.ValidateAsync(new ValidationContext<object>(contextArgument));
|
||||
|
||||
if (validationResult.IsValid) continue;
|
||||
|
||||
foreach (var validationResultError in validationResult.Errors)
|
||||
{
|
||||
if (validationSummery.TryGetValue(validationResultError.PropertyName, out var value))
|
||||
{
|
||||
value.Add(validationResultError.ErrorMessage);
|
||||
continue;
|
||||
}
|
||||
|
||||
validationSummery.Add(validationResultError.PropertyName, new (){validationResultError.ErrorMessage});
|
||||
}
|
||||
}
|
||||
|
||||
if (validationSummery.Count == 0)
|
||||
return await next(context);
|
||||
|
||||
return Results.BadRequest(validationSummery);
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,28 @@
|
||||
using CleanArc.Application.Models.ApiResult;
|
||||
using Microsoft.AspNetCore.Http;
|
||||
|
||||
namespace CleanArc.WebFramework.EndpointFilters;
|
||||
|
||||
public class NotFoundResultEndpointFilter:IEndpointFilter
|
||||
{
|
||||
public async ValueTask<object> InvokeAsync(EndpointFilterInvocationContext context, EndpointFilterDelegate next)
|
||||
{
|
||||
var result = await next(context);
|
||||
|
||||
if (result is not IStatusCodeHttpResult statusCodeResult)
|
||||
{
|
||||
return result;
|
||||
}
|
||||
|
||||
if (statusCodeResult.StatusCode != StatusCodes.Status404NotFound)
|
||||
return result;
|
||||
|
||||
|
||||
if (result is IValueHttpResult valueHttp)
|
||||
{
|
||||
return Results.BadRequest(new ApiResult<object>(false, ApiResultStatusCode.NotFound, valueHttp.Value));
|
||||
}
|
||||
|
||||
return Results.BadRequest(new ApiResult(false, ApiResultStatusCode.NotFound));
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,28 @@
|
||||
using CleanArc.Application.Models.ApiResult;
|
||||
using Microsoft.AspNetCore.Http;
|
||||
|
||||
namespace CleanArc.WebFramework.EndpointFilters;
|
||||
|
||||
public class OkResultEndpointFilter:IEndpointFilter
|
||||
{
|
||||
public async ValueTask<object> InvokeAsync(EndpointFilterInvocationContext context, EndpointFilterDelegate next)
|
||||
{
|
||||
var result=await next(context);
|
||||
|
||||
if (result is not IStatusCodeHttpResult statusCodeResult)
|
||||
{
|
||||
return result;
|
||||
}
|
||||
|
||||
if(statusCodeResult.StatusCode !=StatusCodes.Status200OK)
|
||||
return result;
|
||||
|
||||
|
||||
if (result is IValueHttpResult valueHttp)
|
||||
{
|
||||
return Results.Ok(new ApiResult<object>(true, ApiResultStatusCode.Success, valueHttp.Value));
|
||||
}
|
||||
|
||||
return Results.Ok(new ApiResult(true, ApiResultStatusCode.Success));
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,65 @@
|
||||
using CleanArc.Application.Models.ApiResult;
|
||||
using Microsoft.AspNetCore.Http;
|
||||
using Microsoft.AspNetCore.Mvc;
|
||||
using Microsoft.AspNetCore.Mvc.Filters;
|
||||
|
||||
namespace CleanArc.WebFramework.Filters;
|
||||
|
||||
[Obsolete(message:"Separated filters added")]
|
||||
public class ApiResultFilterAttribute : ResultFilterAttribute
|
||||
{
|
||||
public override void OnResultExecuting(ResultExecutingContext context)
|
||||
{
|
||||
if (context.Result is OkObjectResult okObjectResult)
|
||||
{
|
||||
var apiResult = new ApiResult<object>(true, ApiResultStatusCode.Success, okObjectResult.Value);
|
||||
context.Result = new JsonResult(apiResult) { StatusCode = okObjectResult.StatusCode };
|
||||
}
|
||||
else if (context.Result is OkResult okResult)
|
||||
{
|
||||
var apiResult = new ApiResult(true, ApiResultStatusCode.Success);
|
||||
context.Result = new JsonResult(apiResult) { StatusCode = okResult.StatusCode };
|
||||
}
|
||||
else if (context.Result is BadRequestResult badRequestResult)
|
||||
{
|
||||
var apiResult = new ApiResult(false, ApiResultStatusCode.BadRequest);
|
||||
context.Result = new JsonResult(apiResult) { StatusCode = badRequestResult.StatusCode };
|
||||
context.HttpContext.Response.StatusCode = 400;
|
||||
}
|
||||
else if (context.Result is BadRequestObjectResult badRequestObjectResult)
|
||||
{
|
||||
var message = badRequestObjectResult.Value.ToString();
|
||||
if (badRequestObjectResult.Value is SerializableError errors)
|
||||
{
|
||||
var errorMessages = errors.SelectMany(p => (string[])p.Value).Distinct();
|
||||
message = string.Join(" | ", errorMessages);
|
||||
}
|
||||
var apiResult = new ApiResult(false, ApiResultStatusCode.BadRequest, message);
|
||||
context.Result = new JsonResult(apiResult) { StatusCode = badRequestObjectResult.StatusCode };
|
||||
context.HttpContext.Response.StatusCode = 400;
|
||||
}
|
||||
else if (context.Result is ContentResult contentResult)
|
||||
{
|
||||
var apiResult = new ApiResult(true, ApiResultStatusCode.Success, contentResult.Content);
|
||||
context.Result = new JsonResult(apiResult) { StatusCode = contentResult.StatusCode };
|
||||
}
|
||||
else if (context.Result is NotFoundResultAttribute notFoundResult)
|
||||
{
|
||||
var apiResult = new ApiResult(false, ApiResultStatusCode.NotFound);
|
||||
context.Result = new JsonResult(apiResult) { StatusCode = StatusCodes.Status404NotFound };
|
||||
}
|
||||
else if (context.Result is NotFoundObjectResult notFoundObjectResult)
|
||||
{
|
||||
var apiResult = new ApiResult<object>(false, ApiResultStatusCode.NotFound, notFoundObjectResult.Value);
|
||||
context.Result = new JsonResult(apiResult) { StatusCode = notFoundObjectResult.StatusCode };
|
||||
}
|
||||
else if (context.Result is ObjectResult objectResult && objectResult.StatusCode == null
|
||||
&& !(objectResult.Value is ApiResult))
|
||||
{
|
||||
var apiResult = new ApiResult<object>(true, ApiResultStatusCode.Success, objectResult.Value);
|
||||
context.Result = new JsonResult(apiResult) { StatusCode = objectResult.StatusCode };
|
||||
}
|
||||
|
||||
base.OnResultExecuting(context);
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,37 @@
|
||||
using CleanArc.Application.Models.ApiResult;
|
||||
using CleanArc.SharedKernel.Extensions;
|
||||
using Microsoft.AspNetCore.Http;
|
||||
using Microsoft.AspNetCore.Mvc;
|
||||
using Microsoft.AspNetCore.Mvc.Filters;
|
||||
|
||||
namespace CleanArc.WebFramework.Filters;
|
||||
|
||||
public class BadRequestResultFilterAttribute : ActionFilterAttribute
|
||||
{
|
||||
public override void OnResultExecuting(ResultExecutingContext context)
|
||||
{
|
||||
if (!(context.Result is BadRequestObjectResult badRequestObjectResult)) return;
|
||||
|
||||
var modelState = context.ModelState;
|
||||
|
||||
if (!modelState.IsValid)
|
||||
{
|
||||
var errors = new ValidationProblemDetails(modelState);
|
||||
|
||||
var message = ApiResultStatusCode.BadRequest.ToDisplay();
|
||||
|
||||
var apiResult = new ApiResult<IDictionary<string, string[]>>(false, ApiResultStatusCode.BadRequest, errors.Errors, message);
|
||||
context.Result = new JsonResult(apiResult) { StatusCode = badRequestObjectResult.StatusCode };
|
||||
context.HttpContext.Response.StatusCode = StatusCodes.Status400BadRequest;
|
||||
return;
|
||||
}
|
||||
else
|
||||
{
|
||||
|
||||
|
||||
var apiResult = new ApiResult<object>(false, ApiResultStatusCode.BadRequest,badRequestObjectResult.Value,ApiResultStatusCode.BadRequest.ToDisplay());
|
||||
context.Result = new JsonResult(apiResult) { StatusCode = badRequestObjectResult.StatusCode };
|
||||
context.HttpContext.Response.StatusCode = StatusCodes.Status400BadRequest;
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,15 @@
|
||||
using CleanArc.Application.Models.ApiResult;
|
||||
using Microsoft.AspNetCore.Mvc;
|
||||
using Microsoft.AspNetCore.Mvc.Filters;
|
||||
|
||||
namespace CleanArc.WebFramework.Filters;
|
||||
|
||||
public class ContentResultFilterAttribute : ResultFilterAttribute
|
||||
{
|
||||
public override void OnResultExecuting(ResultExecutingContext context)
|
||||
{
|
||||
if (!(context.Result is ContentResult contentResult)) return;
|
||||
var apiResult = new ApiResult(true, ApiResultStatusCode.Success, contentResult.Content);
|
||||
context.Result = new JsonResult(apiResult) { StatusCode = contentResult.StatusCode };
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,62 @@
|
||||
using CleanArc.Application.Models.ApiResult;
|
||||
using CleanArc.SharedKernel.Extensions;
|
||||
using FluentValidation;
|
||||
using Microsoft.AspNetCore.Mvc;
|
||||
using Microsoft.AspNetCore.Mvc.Filters;
|
||||
using StatusCodes = Microsoft.AspNetCore.Http.StatusCodes;
|
||||
|
||||
namespace CleanArc.WebFramework.Filters;
|
||||
|
||||
public class ModelStateValidationAttribute : ActionFilterAttribute
|
||||
{
|
||||
public override async Task OnActionExecutionAsync(ActionExecutingContext context, ActionExecutionDelegate next)
|
||||
{
|
||||
foreach (var contextActionArgument in context.ActionArguments.Values)
|
||||
{
|
||||
var viewModelValidator =
|
||||
context.HttpContext.RequestServices.GetService(
|
||||
typeof(IValidator<>).MakeGenericType(contextActionArgument.GetType()));
|
||||
|
||||
if (viewModelValidator is IValidator validator)
|
||||
{
|
||||
var validationResult =await validator.ValidateAsync(new ValidationContext<object>(contextActionArgument));
|
||||
|
||||
if (!validationResult.IsValid)
|
||||
{
|
||||
foreach (var validationResultError in validationResult.Errors)
|
||||
{
|
||||
context.ModelState.AddModelError(validationResultError.PropertyName, validationResultError.ErrorMessage);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
var modelState = context.ModelState;
|
||||
|
||||
if (!modelState.IsValid)
|
||||
{
|
||||
|
||||
var model = context.ActionArguments.FirstOrDefault().Value;
|
||||
|
||||
if (model != null)
|
||||
{
|
||||
var errors = new ValidationProblemDetails(modelState);
|
||||
|
||||
var message = ApiResultStatusCode.BadRequest.ToDisplay();
|
||||
|
||||
var apiResult = new ApiResult<IDictionary<string, string[]>>(false, ApiResultStatusCode.BadRequest, errors.Errors, message);
|
||||
context.Result = new JsonResult(apiResult) { StatusCode = StatusCodes.Status400BadRequest };
|
||||
context.HttpContext.Response.StatusCode = StatusCodes.Status400BadRequest;
|
||||
}
|
||||
|
||||
else
|
||||
{
|
||||
var apiResult = new ApiResult(false, ApiResultStatusCode.BadRequest);
|
||||
context.Result = new JsonResult(apiResult) { StatusCode = 400 };
|
||||
context.HttpContext.Response.StatusCode = 400;
|
||||
}
|
||||
}
|
||||
|
||||
await base.OnActionExecutionAsync(context, next);
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,24 @@
|
||||
using CleanArc.Application.Models.ApiResult;
|
||||
using Microsoft.AspNetCore.Http;
|
||||
using Microsoft.AspNetCore.Mvc;
|
||||
using Microsoft.AspNetCore.Mvc.Filters;
|
||||
|
||||
namespace CleanArc.WebFramework.Filters;
|
||||
|
||||
public class NotFoundResultAttribute : ResultFilterAttribute
|
||||
{
|
||||
public override void OnResultExecuting(ResultExecutingContext context)
|
||||
{
|
||||
if ((context.Result is NotFoundObjectResult notFoundObjectResult))
|
||||
{
|
||||
var apiResult = new ApiResult<object>(false, ApiResultStatusCode.NotFound, notFoundObjectResult.Value);
|
||||
context.Result = new JsonResult(apiResult) { StatusCode = notFoundObjectResult.StatusCode };
|
||||
}
|
||||
|
||||
else if(context.Result is NotFoundResult)
|
||||
{
|
||||
var apiResult = new ApiResult(false, ApiResultStatusCode.NotFound);
|
||||
context.Result = new JsonResult(apiResult) { StatusCode =StatusCodes.Status404NotFound };
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,28 @@
|
||||
using CleanArc.Application.Models.ApiResult;
|
||||
using Microsoft.AspNetCore.Mvc;
|
||||
using Microsoft.AspNetCore.Mvc.Filters;
|
||||
|
||||
namespace CleanArc.WebFramework.Filters;
|
||||
|
||||
public class OkResultAttribute:ResultFilterAttribute
|
||||
{
|
||||
public override void OnResultExecuting(ResultExecutingContext context)
|
||||
{
|
||||
switch (context.Result)
|
||||
{
|
||||
case OkObjectResult okObjectResult:
|
||||
{
|
||||
var apiResult = new ApiResult<object>(true, ApiResultStatusCode.Success, okObjectResult.Value);
|
||||
context.Result = new JsonResult(apiResult) { StatusCode = okObjectResult.StatusCode };
|
||||
break;
|
||||
}
|
||||
case OkResult okResult:
|
||||
{
|
||||
var apiResult = new ApiResult(true, ApiResultStatusCode.Success);
|
||||
context.Result = new JsonResult(apiResult) { StatusCode = okResult.StatusCode };
|
||||
break;
|
||||
}
|
||||
default:return;
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,22 @@
|
||||
using CleanArc.Application.Models.ApiResult;
|
||||
using Microsoft.AspNetCore.Http;
|
||||
using Microsoft.AspNetCore.Mvc;
|
||||
|
||||
namespace CleanArc.WebFramework.Filters;
|
||||
|
||||
public class ServerErrorResult:IActionResult
|
||||
{
|
||||
public string Message { get;}
|
||||
|
||||
public ServerErrorResult(string message)
|
||||
{
|
||||
Message = message;
|
||||
}
|
||||
|
||||
public async Task ExecuteResultAsync(ActionContext context)
|
||||
{
|
||||
context.HttpContext.Response.StatusCode = StatusCodes.Status500InternalServerError;
|
||||
var response = new ApiResult(false, ApiResultStatusCode.ServerError, Message);
|
||||
await context.HttpContext.Response.WriteAsJsonAsync(response);
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,63 @@
|
||||
using CleanArc.Application.Models.ApiResult;
|
||||
using Microsoft.AspNetCore.Diagnostics;
|
||||
using Microsoft.AspNetCore.Http;
|
||||
using CleanArc.SharedKernel.Extensions;
|
||||
using Microsoft.AspNetCore.Hosting;
|
||||
using Microsoft.Extensions.Hosting;
|
||||
using Microsoft.Extensions.Logging;
|
||||
|
||||
namespace CleanArc.WebFramework.Middlewares;
|
||||
|
||||
public class ExceptionHandler(ILogger<ExceptionHandler> logger,IWebHostEnvironment environment) : IExceptionHandler
|
||||
{
|
||||
|
||||
public async ValueTask<bool> TryHandleAsync(HttpContext context, Exception exception, CancellationToken cancellationToken)
|
||||
{
|
||||
if (environment.IsDevelopment())
|
||||
return false;
|
||||
|
||||
if (exception is FluentValidation.ValidationException validationException)
|
||||
{
|
||||
context.Response.StatusCode = StatusCodes.Status422UnprocessableEntity;
|
||||
|
||||
var errors = new Dictionary<string, List<string>>();
|
||||
|
||||
foreach (var validationExceptionError in validationException.Errors)
|
||||
{
|
||||
if (!errors.ContainsKey(validationExceptionError.PropertyName))
|
||||
errors.Add(validationExceptionError.PropertyName, new List<string>() { validationExceptionError.ErrorMessage });
|
||||
else
|
||||
errors[validationExceptionError.PropertyName].Add(validationExceptionError.ErrorMessage);
|
||||
|
||||
}
|
||||
|
||||
var apiResult = new ApiResult<IDictionary<string, List<string>>>(false, ApiResultStatusCode.EntityProcessError, errors, ApiResultStatusCode.EntityProcessError.ToDisplay());
|
||||
|
||||
context.Response.ContentType = "application/problem+json";
|
||||
await context.Response.WriteAsJsonAsync(apiResult, cancellationToken: cancellationToken);
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
var exceptionFeature = context.Features.Get<IExceptionHandlerPathFeature>();
|
||||
|
||||
if (exceptionFeature is not null)
|
||||
logger.LogError(exceptionFeature.Error,
|
||||
"Unhandled exception occured. Path: {exceptionUrlPath} ."
|
||||
, exceptionFeature.Path
|
||||
);
|
||||
|
||||
else
|
||||
logger.LogError(exception, "Error captured in global exception handler");
|
||||
|
||||
context.Response.StatusCode = StatusCodes.Status500InternalServerError;
|
||||
|
||||
context.Response.ContentType = "application/problem+json";
|
||||
var response = new ApiResult(false,
|
||||
ApiResultStatusCode.ServerError, "Internal Server Error");
|
||||
context.Response.StatusCode = StatusCodes.Status500InternalServerError;
|
||||
await context.Response.WriteAsJsonAsync(response, cancellationToken: cancellationToken);
|
||||
|
||||
return true;
|
||||
}
|
||||
}
|
||||
+45
@@ -0,0 +1,45 @@
|
||||
using Asp.Versioning;
|
||||
using Microsoft.Extensions.DependencyInjection;
|
||||
|
||||
namespace CleanArc.WebFramework.ServiceConfiguration;
|
||||
|
||||
public static class ServiceCollectionExtension
|
||||
{
|
||||
public static IServiceCollection AddWebFrameworkServices(this IServiceCollection services)
|
||||
{
|
||||
services.AddApiVersioning(options =>
|
||||
{
|
||||
//url segment => {version}
|
||||
options.AssumeDefaultVersionWhenUnspecified = true; //default => false;
|
||||
options.DefaultApiVersion = new ApiVersion(1, 0); //v1.0 == v1
|
||||
options.ReportApiVersions = true;
|
||||
|
||||
//ApiVersion.TryParse("1.0", out var version10);
|
||||
//ApiVersion.TryParse("1", out var version1);
|
||||
//var a = version10 == version1;
|
||||
|
||||
//options.ApiVersionReader = new QueryStringApiVersionReader("api-version");
|
||||
// api/posts?api-version=1
|
||||
|
||||
//options.ApiVersionReader = new UrlSegmentApiVersionReader();
|
||||
// api/v1/posts
|
||||
|
||||
//options.ApiVersionReader = new HeaderApiVersionReader(new[] { "Api-Version" });
|
||||
// header => Api-Version : 1
|
||||
|
||||
//options.ApiVersionReader = new MediaTypeApiVersionReader()
|
||||
|
||||
//options.ApiVersionReader = ApiVersionReader.Combine(new QueryStringApiVersionReader("api-version"), new UrlSegmentApiVersionReader())
|
||||
// combine of [querystring] & [urlsegment]
|
||||
}).AddMvc()
|
||||
.AddApiExplorer(options =>
|
||||
{
|
||||
options.GroupNameFormat = "'v'V";
|
||||
options.SubstituteApiVersionInUrl = true;
|
||||
});;
|
||||
|
||||
return services;
|
||||
|
||||
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,25 @@
|
||||
using CleanArc.SharedKernel.Extensions;
|
||||
using NSwag.Generation.Processors;
|
||||
using NSwag.Generation.Processors.Contexts;
|
||||
|
||||
namespace CleanArc.WebFramework.Swagger;
|
||||
|
||||
public class ApiVersionDocumentProcessor: IDocumentProcessor
|
||||
{
|
||||
public void Process(DocumentProcessorContext context)
|
||||
{
|
||||
// Filter out operations that do not match the current document version
|
||||
var version = context.Document.Info.Version; // e.g., "v1"
|
||||
|
||||
var pathsToRemove = context.Document.Paths
|
||||
.Where(pathItem => !RegExHelpers.MatchesApiVersion( version,pathItem.Key))
|
||||
.Select(path => path.Key)
|
||||
.ToList();
|
||||
|
||||
|
||||
foreach (var path in pathsToRemove)
|
||||
{
|
||||
context.Document.Paths.Remove(path);
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,122 @@
|
||||
using NSwag.Generation.Processors;
|
||||
using NSwag.Generation.Processors.Contexts;
|
||||
using Pluralize.NET;
|
||||
|
||||
|
||||
namespace CleanArc.WebFramework.Swagger;
|
||||
|
||||
public class ApplySummariesOperationFilter : IOperationProcessor
|
||||
{
|
||||
|
||||
|
||||
public bool Process(OperationProcessorContext context)
|
||||
{
|
||||
|
||||
if (context.ControllerType is null)
|
||||
return true;
|
||||
|
||||
var actionName = context.MethodInfo.Name;
|
||||
var controllerName = context.ControllerType.Name.Replace("Controller", "");
|
||||
|
||||
var pluralizer = new Pluralizer();
|
||||
|
||||
var singularizeName =pluralizer.Singularize(controllerName);
|
||||
var pluralizeName = pluralizer.Pluralize(singularizeName);
|
||||
|
||||
var parameterCount = context.OperationDescription.Operation.Parameters
|
||||
.Count(p => p.Name != "version" && p.Name != "api-version");
|
||||
|
||||
if (IsGetAllAction(actionName, pluralizeName, singularizeName, parameterCount))
|
||||
{
|
||||
if (string.IsNullOrEmpty(context.OperationDescription.Operation.Summary))
|
||||
{
|
||||
context.OperationDescription.Operation.Summary = $"Returns all {pluralizeName}";
|
||||
}
|
||||
}
|
||||
else if (IsActionName(actionName, singularizeName, "Post", "Create"))
|
||||
{
|
||||
if (string.IsNullOrEmpty(context.OperationDescription.Operation.Summary))
|
||||
{
|
||||
context.OperationDescription.Operation.Summary = $"Creates a {singularizeName}";
|
||||
}
|
||||
|
||||
if (context.OperationDescription.Operation.Parameters.Count > 0 &&
|
||||
string.IsNullOrEmpty(context.OperationDescription.Operation.Parameters[0].Description))
|
||||
{
|
||||
context.OperationDescription.Operation.Parameters[0].Description = $"A {singularizeName} representation";
|
||||
}
|
||||
}
|
||||
else if (IsActionName(actionName, singularizeName, "Read", "Get"))
|
||||
{
|
||||
if (string.IsNullOrEmpty(context.OperationDescription.Operation.Summary))
|
||||
{
|
||||
context.OperationDescription.Operation.Summary = $"Retrieves a {singularizeName} by unique id";
|
||||
}
|
||||
|
||||
if (context.OperationDescription.Operation.Parameters.Count > 0 &&
|
||||
string.IsNullOrEmpty(context.OperationDescription.Operation.Parameters[0].Description))
|
||||
{
|
||||
context.OperationDescription.Operation.Parameters[0].Description = $"A unique id for the {singularizeName}";
|
||||
}
|
||||
}
|
||||
else if (IsActionName(actionName, singularizeName, "Put", "Edit", "Update"))
|
||||
{
|
||||
if (string.IsNullOrEmpty(context.OperationDescription.Operation.Summary))
|
||||
{
|
||||
context.OperationDescription.Operation.Summary = $"Updates a {singularizeName} by unique id";
|
||||
}
|
||||
|
||||
if (context.OperationDescription.Operation.Parameters.Count > 0 &&
|
||||
string.IsNullOrEmpty(context.OperationDescription.Operation.Parameters[0].Description))
|
||||
{
|
||||
context.OperationDescription.Operation.Parameters[0].Description = $"A {singularizeName} representation";
|
||||
}
|
||||
}
|
||||
else if (IsActionName(actionName, singularizeName, "Delete", "Remove"))
|
||||
{
|
||||
if (string.IsNullOrEmpty(context.OperationDescription.Operation.Summary))
|
||||
{
|
||||
context.OperationDescription.Operation.Summary = $"Deletes a {singularizeName} by unique id";
|
||||
}
|
||||
|
||||
if (context.OperationDescription.Operation.Parameters.Count > 0 &&
|
||||
string.IsNullOrEmpty(context.OperationDescription.Operation.Parameters[0].Description))
|
||||
{
|
||||
context.OperationDescription.Operation.Parameters[0].Description = $"A unique id for the {singularizeName}";
|
||||
}
|
||||
}
|
||||
|
||||
return true;
|
||||
}
|
||||
private bool IsActionName(string actionName, string singularizeName, params string[] names)
|
||||
{
|
||||
foreach (var name in names)
|
||||
{
|
||||
if (actionName.Equals(name, StringComparison.OrdinalIgnoreCase) ||
|
||||
actionName.Equals($"{name}ById", StringComparison.OrdinalIgnoreCase) ||
|
||||
actionName.Equals($"{name}{singularizeName}", StringComparison.OrdinalIgnoreCase) ||
|
||||
actionName.Equals($"{name}{singularizeName}ById", StringComparison.OrdinalIgnoreCase))
|
||||
{
|
||||
return true;
|
||||
}
|
||||
}
|
||||
return false;
|
||||
}
|
||||
private bool IsGetAllAction(string actionName, string pluralizeName, string singularizeName, int parameterCount)
|
||||
{
|
||||
var actionNames = new[] { "Get", "Read", "Select" };
|
||||
foreach (var name in actionNames)
|
||||
{
|
||||
if ((actionName.Equals(name, StringComparison.OrdinalIgnoreCase) && parameterCount == 0) ||
|
||||
actionName.Equals($"{name}All", StringComparison.OrdinalIgnoreCase) ||
|
||||
actionName.Equals($"{name}{pluralizeName}", StringComparison.OrdinalIgnoreCase) ||
|
||||
actionName.Equals($"{name}All{singularizeName}", StringComparison.OrdinalIgnoreCase) ||
|
||||
actionName.Equals($"{name}All{pluralizeName}", StringComparison.OrdinalIgnoreCase))
|
||||
{
|
||||
return true;
|
||||
}
|
||||
}
|
||||
return false;
|
||||
}
|
||||
|
||||
}
|
||||
@@ -0,0 +1,36 @@
|
||||
using NSwag;
|
||||
using NSwag.Generation.Processors;
|
||||
using NSwag.Generation.Processors.Contexts;
|
||||
|
||||
|
||||
namespace CleanArc.WebFramework.Swagger;
|
||||
|
||||
public class CustomTokenRequiredOperationFilter : IOperationProcessor
|
||||
{
|
||||
|
||||
public bool Process(OperationProcessorContext context)
|
||||
{
|
||||
var hasAttribute = context.MethodInfo
|
||||
.GetCustomAttributes(typeof(RequireTokenWithoutAuthorizationAttribute), false).Any();
|
||||
|
||||
if (hasAttribute)
|
||||
{
|
||||
// Add security requirements to the operation
|
||||
var securityScheme = new OpenApiSecurityScheme
|
||||
{
|
||||
Type = OpenApiSecuritySchemeType.Http,
|
||||
Scheme = "bearer",
|
||||
BearerFormat = "JWT",
|
||||
In = OpenApiSecurityApiKeyLocation.Header,
|
||||
Name = "Authorization"
|
||||
};
|
||||
|
||||
var securityRequirement = new OpenApiSecurityRequirement { { securityScheme.Scheme, new List<string>() } };
|
||||
|
||||
|
||||
context.OperationDescription.Operation.Security=[securityRequirement];
|
||||
}
|
||||
|
||||
return true;
|
||||
}
|
||||
}
|
||||
+11
@@ -0,0 +1,11 @@
|
||||
namespace CleanArc.WebFramework.Swagger;
|
||||
|
||||
|
||||
/// <summary>
|
||||
/// Marker Attribute for Custom Actions or controllers that need token but without authorization check
|
||||
/// </summary>
|
||||
[AttributeUsage(AttributeTargets.Class| AttributeTargets.Method)]
|
||||
public class RequireTokenWithoutAuthorizationAttribute : Attribute
|
||||
{
|
||||
|
||||
};
|
||||
@@ -0,0 +1,69 @@
|
||||
using Microsoft.AspNetCore.Authentication.JwtBearer;
|
||||
using Microsoft.AspNetCore.Builder;
|
||||
using Microsoft.Extensions.Configuration;
|
||||
using Microsoft.Extensions.DependencyInjection;
|
||||
using NSwag;
|
||||
|
||||
namespace CleanArc.WebFramework.Swagger;
|
||||
|
||||
public static class SwaggerConfigurationExtensions
|
||||
{
|
||||
public static void AddSwagger(this IServiceCollection services,
|
||||
params string[] versions)
|
||||
{
|
||||
ArgumentNullException.ThrowIfNull(services, nameof(services));
|
||||
|
||||
|
||||
foreach (var version in versions)
|
||||
{
|
||||
services.AddOpenApiDocument(options =>
|
||||
{
|
||||
options.Title = "Clean Architecture OpenAPI docs";
|
||||
options.Version = version;
|
||||
options.DocumentName = version;
|
||||
|
||||
|
||||
options.AddSecurity("Bearer", new NSwag.OpenApiSecurityScheme()
|
||||
{
|
||||
Description = "Enter JWT Token ONLY",
|
||||
In = OpenApiSecurityApiKeyLocation.Header,
|
||||
Name = "Authorization",
|
||||
Type = OpenApiSecuritySchemeType.Http,
|
||||
Scheme = JwtBearerDefaults.AuthenticationScheme,
|
||||
});
|
||||
|
||||
options.DocumentProcessors.Add(new ApiVersionDocumentProcessor());
|
||||
options.OperationProcessors.Add(new ApplySummariesOperationFilter());
|
||||
options.OperationProcessors.Add(new CustomTokenRequiredOperationFilter());
|
||||
|
||||
options.OperationProcessors.Add(
|
||||
new NSwag.Generation.Processors.Security.AspNetCoreOperationSecurityScopeProcessor("Bearer"));
|
||||
|
||||
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
public static void UseSwaggerAndUi(this WebApplication app)
|
||||
{
|
||||
ArgumentNullException.ThrowIfNull(app, nameof(app));
|
||||
|
||||
app.UseOpenApi();
|
||||
|
||||
app.UseSwaggerUi(options =>
|
||||
{
|
||||
options.PersistAuthorization = true;
|
||||
|
||||
options.EnableTryItOut = true;
|
||||
|
||||
options.Path = "/swagger";
|
||||
|
||||
});
|
||||
|
||||
app.UseReDoc(settings =>
|
||||
{
|
||||
settings.Path = "/api-docs/{documentName}";
|
||||
settings.DocumentTitle = "Clean Architecture API doc sample";
|
||||
});
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,26 @@
|
||||
<Project Sdk="Microsoft.NET.Sdk">
|
||||
|
||||
<PropertyGroup>
|
||||
<TargetFramework>net10.0</TargetFramework>
|
||||
<ImplicitUsings>enable</ImplicitUsings>
|
||||
</PropertyGroup>
|
||||
|
||||
<ItemGroup>
|
||||
<PackageReference Include="Grpc.AspNetCore" />
|
||||
<PackageReference Include="Grpc.AspNetCore.Server.Reflection" />
|
||||
</ItemGroup>
|
||||
|
||||
<ItemGroup>
|
||||
<ProjectReference Include="..\..\..\Core\CleanArc.Application\CleanArc.Application.csproj" />
|
||||
</ItemGroup>
|
||||
|
||||
<ItemGroup>
|
||||
<Protobuf Include="ProtoModels\UserGrpcServiceModels.proto" GrpcServices="Server" />
|
||||
<Protobuf Include="ProtoModels\OrderGrpcServiceModels.proto" GrpcServices="Server" />
|
||||
</ItemGroup>
|
||||
|
||||
|
||||
<ItemGroup>
|
||||
<FrameworkReference Include="Microsoft.AspNetCore.App" />
|
||||
</ItemGroup>
|
||||
</Project>
|
||||
@@ -0,0 +1,39 @@
|
||||
using Microsoft.AspNetCore.Builder;
|
||||
using Microsoft.AspNetCore.Http;
|
||||
using Microsoft.Extensions.DependencyInjection;
|
||||
using CleanArc.Web.Plugins.Grpc.Services;
|
||||
|
||||
namespace CleanArc.Web.Plugins.Grpc;
|
||||
|
||||
public static class GrpcPluginStartup
|
||||
{
|
||||
public static IServiceCollection ConfigureGrpcPluginServices(this IServiceCollection services)
|
||||
{
|
||||
|
||||
|
||||
services.AddGrpc();
|
||||
services.AddGrpcReflection();
|
||||
|
||||
return services;
|
||||
}
|
||||
|
||||
public static void ConfigureGrpcPipeline(this WebApplication app)
|
||||
{
|
||||
|
||||
app.MapGrpcService<UserGrpcServices>();
|
||||
app.MapGrpcService<OrderGrpcServices>();
|
||||
app.MapGrpcReflectionService();
|
||||
|
||||
app.MapGet("/GrpcUser", async context =>
|
||||
{
|
||||
await context.Response.WriteAsync(
|
||||
"Communication with this gRPC endpoint must be made through a gRPC client.");
|
||||
});
|
||||
|
||||
app.MapGet("/GrpcUserOrder", async context =>
|
||||
{
|
||||
await context.Response.WriteAsync(
|
||||
"Communication with this gRPC endpoint must be made through a gRPC client.");
|
||||
});
|
||||
}
|
||||
}
|
||||
+17
@@ -0,0 +1,17 @@
|
||||
syntax = "proto3";
|
||||
|
||||
option csharp_namespace = "CleanArc.Web.Plugins.Grpc.ProtoModels";
|
||||
import "google/protobuf/empty.proto";
|
||||
|
||||
package GrpcOrderController;
|
||||
|
||||
|
||||
service OrderServices {
|
||||
rpc GetUserOrders(google.protobuf.Empty) returns (stream GetUserOrdersModel);
|
||||
}
|
||||
|
||||
message GetUserOrdersModel{
|
||||
int32 OrderId=1;
|
||||
string OrderName=2;
|
||||
}
|
||||
|
||||
+44
@@ -0,0 +1,44 @@
|
||||
|
||||
syntax = "proto3";
|
||||
|
||||
option csharp_namespace = "CleanArc.Web.Plugins.Grpc.ProtoModels";
|
||||
|
||||
package GrpcUserController;
|
||||
|
||||
service UserServices {
|
||||
rpc TokenRequest(UserTokenRequest) returns (TokenRequestResult);
|
||||
rpc GetUserToken(GetUserTokenRequestModel) returns (GetUserTokenRequestResult);
|
||||
}
|
||||
|
||||
message UserTokenRequest{
|
||||
string PhoneNumber=1;
|
||||
}
|
||||
|
||||
message TokenRequestResult{
|
||||
string Message=1;
|
||||
bool IsSuccess=2;
|
||||
TokenRequestResultModel UserTokenRequestResult=3;
|
||||
}
|
||||
|
||||
message TokenRequestResultModel{
|
||||
string UserKey=1;
|
||||
}
|
||||
|
||||
message GetUserTokenRequestModel
|
||||
{
|
||||
string UserKey=1;
|
||||
string Code=2;
|
||||
}
|
||||
|
||||
message GetUserTokenRequestResult{
|
||||
string Message=1;
|
||||
bool IsSuccess=2;
|
||||
UserToken Token=3;
|
||||
}
|
||||
|
||||
message UserToken{
|
||||
string AccessToken=1;
|
||||
string RefreshToken=2;
|
||||
string TokenType=3;
|
||||
int32 ExpiresIn=4;
|
||||
}
|
||||
@@ -0,0 +1,46 @@
|
||||
using CleanArc.Application.Features.Order.Queries.GetUserOrders;
|
||||
using CleanArc.SharedKernel.Extensions;
|
||||
using CleanArc.Web.Plugins.Grpc.ProtoModels;
|
||||
using Google.Protobuf.WellKnownTypes;
|
||||
using Grpc.Core;
|
||||
using Mediator;
|
||||
using Microsoft.AspNetCore.Authorization;
|
||||
|
||||
namespace CleanArc.Web.Plugins.Grpc.Services
|
||||
{
|
||||
[Authorize]
|
||||
public class OrderGrpcServices:OrderServices.OrderServicesBase
|
||||
{
|
||||
|
||||
|
||||
private readonly ISender _sender;
|
||||
|
||||
public OrderGrpcServices(ISender sender)
|
||||
{
|
||||
_sender = sender;
|
||||
}
|
||||
|
||||
public override async Task GetUserOrders(Empty request, IServerStreamWriter<GetUserOrdersModel> responseStream, ServerCallContext context)
|
||||
{
|
||||
var userId = int.Parse(context.GetHttpContext().User.Identity.GetUserId());
|
||||
|
||||
var query = await _sender.Send(new GetUserOrdersQueryModel(userId));
|
||||
|
||||
if (!query.IsSuccess)
|
||||
{
|
||||
context.Status = new Status(StatusCode.InvalidArgument, query.GetErrorMessage());
|
||||
return;
|
||||
}
|
||||
|
||||
foreach (var getUsersQueryResultModel in query.Result)
|
||||
{
|
||||
await responseStream.WriteAsync(new GetUserOrdersModel()
|
||||
{ OrderId = getUsersQueryResultModel.OrderId, OrderName = getUsersQueryResultModel.OrderName });
|
||||
|
||||
await Task.Delay(400);
|
||||
|
||||
}
|
||||
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,83 @@
|
||||
using CleanArc.Application.Features.Users.Queries.GenerateUserToken;
|
||||
using CleanArc.Application.Features.Users.Queries.TokenRequest;
|
||||
using CleanArc.Web.Plugins.Grpc.ProtoModels;
|
||||
using Grpc.Core;
|
||||
using Mediator;
|
||||
|
||||
namespace CleanArc.Web.Plugins.Grpc.Services;
|
||||
|
||||
public class UserGrpcServices : UserServices.UserServicesBase
|
||||
{
|
||||
private readonly IMediator _mediator;
|
||||
|
||||
public UserGrpcServices(IMediator mediator)
|
||||
{
|
||||
_mediator = mediator;
|
||||
}
|
||||
|
||||
public override async Task<TokenRequestResult> TokenRequest(UserTokenRequest request, ServerCallContext context)
|
||||
{
|
||||
if (request is null)
|
||||
{
|
||||
context.Status = new Status(StatusCode.InvalidArgument, "Required Arguments Not Found");
|
||||
|
||||
return new TokenRequestResult() { IsSuccess = false, Message = "Input Model Not Found" };
|
||||
}
|
||||
|
||||
var tokenQuery = await _mediator.Send(new UserTokenRequestQuery(request.PhoneNumber));
|
||||
|
||||
if (!tokenQuery.IsSuccess)
|
||||
{
|
||||
context.Status = new Status(StatusCode.InvalidArgument, "User not found");
|
||||
|
||||
return new TokenRequestResult()
|
||||
{ IsSuccess = false, Message = tokenQuery.GetErrorMessage(), UserTokenRequestResult = null };
|
||||
}
|
||||
|
||||
if (tokenQuery.IsNotFound)
|
||||
{
|
||||
context.Status = new Status(StatusCode.NotFound, "User Not Found");
|
||||
|
||||
return new TokenRequestResult()
|
||||
{ IsSuccess = false, Message = tokenQuery.GetErrorMessage(), UserTokenRequestResult = null };
|
||||
}
|
||||
|
||||
return new TokenRequestResult()
|
||||
{
|
||||
IsSuccess = true,
|
||||
Message = string.Empty,
|
||||
UserTokenRequestResult = new TokenRequestResultModel() { UserKey = tokenQuery.Result.UserKey }
|
||||
};
|
||||
}
|
||||
|
||||
public override async Task<GetUserTokenRequestResult> GetUserToken(GetUserTokenRequestModel request, ServerCallContext context)
|
||||
{
|
||||
|
||||
if (request is null)
|
||||
{
|
||||
context.Status = new Status(StatusCode.InvalidArgument, "Required Arguments Not Found");
|
||||
|
||||
return new GetUserTokenRequestResult() { IsSuccess = false, Message = "Input Model Not Found" };
|
||||
}
|
||||
|
||||
var tokenQuery = await _mediator.Send(new GenerateUserTokenQuery(request.UserKey, request.Code));
|
||||
|
||||
if (!tokenQuery.IsSuccess)
|
||||
{
|
||||
context.Status = new Status(StatusCode.InvalidArgument, tokenQuery.GetErrorMessage());
|
||||
|
||||
return new GetUserTokenRequestResult() { IsSuccess = false, Message = tokenQuery.GetErrorMessage() };
|
||||
}
|
||||
|
||||
|
||||
return new GetUserTokenRequestResult()
|
||||
{
|
||||
IsSuccess = true, Message = string.Empty,
|
||||
Token = new UserToken()
|
||||
{
|
||||
AccessToken = tokenQuery.Result.access_token, ExpiresIn = tokenQuery.Result.expires_in,
|
||||
RefreshToken = tokenQuery.Result.refresh_token, TokenType = tokenQuery.Result.token_type
|
||||
}
|
||||
};
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user