(feature): custom json model binder
This commit is contained in:
parent
cac3d2502e
commit
023d3b065c
@ -10,7 +10,7 @@
|
||||
"address": {
|
||||
"street": "123 456th St",
|
||||
"city": "New York",
|
||||
"region": "NY"
|
||||
"region": "NY",
|
||||
"postCode": "10001",
|
||||
"country": "US"
|
||||
},
|
||||
@ -21,7 +21,7 @@
|
||||
"passwordRecoverySettings": {
|
||||
"smtpSettings": {
|
||||
"server": "smtp.ionos.it",
|
||||
"port": 587
|
||||
"port": 587,
|
||||
"useSsl": true,
|
||||
"userName": "commercial@maks-it.com",
|
||||
"password": "nECbzrWqwzM5Lv4zCxV91g=="
|
||||
@ -41,7 +41,6 @@
|
||||
"paragraphs": [
|
||||
"Your recovery link: {{recoveryLink}}"
|
||||
]
|
||||
|
||||
}
|
||||
}
|
||||
]
|
||||
|
||||
@ -114,7 +114,10 @@
|
||||
<table role="presentation" border="0" cellpadding="0" cellspacing="0" style="border-collapse: separate; mso-table-lspace: 0pt; mso-table-rspace: 0pt; width: 100%;" width="100%">
|
||||
<tr>
|
||||
<td style="font-family: sans-serif; font-size: 14px; vertical-align: top;" valign="top">
|
||||
{{paragraphs}}
|
||||
<p style="font-family: sans-serif; font-size: 14px; font-weight: normal; margin: 0; margin-bottom: 15px;">Hello, {{userName}}</p>
|
||||
<p style="font-family: sans-serif; font-size: 14px; font-weight: normal; margin: 0; margin-bottom: 15px;">We have recieved your account password recovery request.<br />
|
||||
In case if it wasn't you, please ignore this email.</p>
|
||||
<p style="font-family: sans-serif; font-size: 14px; font-weight: normal; margin: 0; margin-bottom: 15px;">Your password recovery token: {{passwordRecoveryToken}}</p>
|
||||
</td>
|
||||
</tr>
|
||||
</table>
|
||||
36
src/Core/Binders/JsonModelBinder.cs
Normal file
36
src/Core/Binders/JsonModelBinder.cs
Normal file
@ -0,0 +1,36 @@
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.Linq;
|
||||
using System.Text;
|
||||
using System.Threading.Tasks;
|
||||
|
||||
namespace Core.Binders {
|
||||
using System;
|
||||
using System.Threading.Tasks;
|
||||
using Extensions;
|
||||
using Microsoft.AspNetCore.Mvc.ModelBinding;
|
||||
|
||||
public class JsonModelBinder<T> : IModelBinder {
|
||||
public Task BindModelAsync(ModelBindingContext bindingContext) {
|
||||
if (bindingContext == null) {
|
||||
throw new ArgumentNullException(nameof(bindingContext));
|
||||
}
|
||||
|
||||
// Check the value sent in
|
||||
var valueProviderResult = bindingContext.ValueProvider.GetValue(bindingContext.ModelName);
|
||||
if (valueProviderResult != ValueProviderResult.None) {
|
||||
bindingContext.ModelState.SetModelValue(bindingContext.ModelName, valueProviderResult);
|
||||
|
||||
// Attempt to convert the input value
|
||||
var valueAsString = valueProviderResult.FirstValue;
|
||||
var result = valueAsString.ToObject<T>();
|
||||
if (result != null) {
|
||||
bindingContext.Result = ModelBindingResult.Success(result);
|
||||
return Task.CompletedTask;
|
||||
}
|
||||
}
|
||||
|
||||
return Task.CompletedTask;
|
||||
}
|
||||
}
|
||||
}
|
||||
15
src/Core/Converters/EnumerationDisplayNameConverter.cs
Normal file
15
src/Core/Converters/EnumerationDisplayNameConverter.cs
Normal file
@ -0,0 +1,15 @@
|
||||
using System.Text.Json;
|
||||
using System.Text.Json.Serialization;
|
||||
|
||||
using Core.Abstractions;
|
||||
|
||||
namespace Core.Converters;
|
||||
|
||||
public class EnumerationDisplayNameConverter<T> : JsonConverter<T> where T : Enumeration {
|
||||
public override T Read(ref Utf8JsonReader reader, Type typeToConvert, JsonSerializerOptions options) =>
|
||||
Enumeration.FromDisplayName<T>(reader.GetString() ?? "");
|
||||
|
||||
public override void Write(Utf8JsonWriter writer, T value, JsonSerializerOptions options) =>
|
||||
writer.WriteStringValue(value.Name);
|
||||
}
|
||||
|
||||
13
src/Core/Converters/EnumerationIdConverter.cs
Normal file
13
src/Core/Converters/EnumerationIdConverter.cs
Normal file
@ -0,0 +1,13 @@
|
||||
using System.Text.Json;
|
||||
using System.Text.Json.Serialization;
|
||||
|
||||
using Core.Abstractions;
|
||||
|
||||
namespace Core.Converters;
|
||||
public class EnumerationIdConverter<T> : JsonConverter<T> where T : Enumeration {
|
||||
public override T Read(ref Utf8JsonReader reader, Type typeToConvert, JsonSerializerOptions options) =>
|
||||
Enumeration.FromValue<T>(reader.GetInt32());
|
||||
|
||||
public override void Write(Utf8JsonWriter writer, T value, JsonSerializerOptions options) =>
|
||||
writer.WriteNumberValue(value.Id);
|
||||
}
|
||||
@ -13,7 +13,7 @@ namespace Core.Enumerations {
|
||||
public static readonly Errors WrongOrNotManaged = new(2, "is wrong or not managed");
|
||||
public static readonly Errors NullOrEmpty = new(3, "is null or empty");
|
||||
public static readonly Errors NotMatched = new(4, "not matched");
|
||||
|
||||
public static readonly Errors UnableToParse = new(5, "unable to parse");
|
||||
|
||||
private Errors(int id, string displayName) : base(id, displayName) { }
|
||||
}
|
||||
|
||||
20
src/Core/Enumerations/TemplateTypes.cs
Normal file
20
src/Core/Enumerations/TemplateTypes.cs
Normal file
@ -0,0 +1,20 @@
|
||||
using Core.Abstractions;
|
||||
using Newtonsoft.Json.Linq;
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.Diagnostics.CodeAnalysis;
|
||||
using System.Globalization;
|
||||
using System.Linq;
|
||||
using System.Text;
|
||||
using System.Threading.Tasks;
|
||||
|
||||
namespace Core.Enumerations;
|
||||
|
||||
public class TemplateTypes : Enumeration {
|
||||
|
||||
public static readonly TemplateTypes Unknown = new(-1, "Unknown");
|
||||
public static readonly TemplateTypes PasswordRecovery = new(0, "PasswordRecovery");
|
||||
public static readonly TemplateTypes EmailConfirmation = new(1, "EmailConfirmation");
|
||||
|
||||
private TemplateTypes(int id, string displayName) : base(id, displayName) { }
|
||||
}
|
||||
@ -52,6 +52,8 @@ public class Password : DomainObjectBase<Password> {
|
||||
/// <returns></returns>
|
||||
public string Recovery() {
|
||||
var recoveryToken = new PasswordRecoveryToken();
|
||||
|
||||
RecoveryTokens ??= new List<PasswordRecoveryToken>();
|
||||
RecoveryTokens.Add(recoveryToken);
|
||||
|
||||
return recoveryToken.Value;
|
||||
@ -64,7 +66,7 @@ public class Password : DomainObjectBase<Password> {
|
||||
/// <param name="password"></param>
|
||||
/// <returns></returns>
|
||||
public bool Reset(string token, string password) {
|
||||
if (RecoveryTokens.Any(x => x.Value == token && !(DateTime.UtcNow > x.Expires))) {
|
||||
if (RecoveryTokens != null && RecoveryTokens.Any(x => x.Value == token && !(DateTime.UtcNow > x.Expires))) {
|
||||
Set(password);
|
||||
RecoveryTokens.Clear();
|
||||
|
||||
|
||||
@ -7,8 +7,6 @@ using WeatherForecast.Models.Account.Requests;
|
||||
using WeatherForecast.Policies;
|
||||
using WeatherForecast.Services;
|
||||
using DomainObjects.Documents.Users;
|
||||
using Microsoft.AspNetCore.Identity;
|
||||
using Org.BouncyCastle.Asn1.Ocsp;
|
||||
using DomainResults.Common;
|
||||
|
||||
namespace WeatherForecast.Controllers;
|
||||
@ -107,7 +105,7 @@ public class AccountController : ControllerBase {
|
||||
if ((await _authorizationService.AuthorizeAsync(User, new List<User> { user }, new PasswordChangeRequirement {
|
||||
OldPassword = requestData.OldPassword
|
||||
})).Succeeded) {
|
||||
var result = _accountService.PasswordChange(user, requestData.OldPassword, requestData.NewPassword);
|
||||
var result = _accountService.PasswordChange(user, requestData);
|
||||
return result.ToActionResult();
|
||||
}
|
||||
|
||||
|
||||
@ -7,6 +7,8 @@ using DomainResults.Mvc;
|
||||
|
||||
using DataProviders.Buckets;
|
||||
using WeatherForecast.Services;
|
||||
using WeatherForecast.Models.Template.Requests;
|
||||
using Core.Binders;
|
||||
|
||||
namespace WeatherForecast.Controllers;
|
||||
|
||||
@ -38,10 +40,11 @@ public class TemplateController : ControllerBase {
|
||||
/// Allows to upload private dkim certificate
|
||||
/// </summary>
|
||||
/// <param name="siteId"></param>
|
||||
/// <param name="requestData"></param>
|
||||
/// <param name="file"></param>
|
||||
/// <returns></returns>
|
||||
[HttpPost("{siteId}")]
|
||||
public IActionResult Post([FromRoute] Guid siteId, IFormFile file) {
|
||||
public IActionResult Post([FromRoute] Guid siteId, [ModelBinder(typeof(JsonModelBinder<PostTemplateRequestModel>))] PostTemplateRequestModel requestData, IFormFile file) {
|
||||
|
||||
if (!(file.Length > 0))
|
||||
return IDomainResult.Failed().ToActionResult();
|
||||
@ -49,11 +52,35 @@ public class TemplateController : ControllerBase {
|
||||
using var ms = new MemoryStream();
|
||||
file.CopyTo(ms);
|
||||
|
||||
var result = _templateService.Post(new BucketFile(siteId, file.FileName, ms.ToArray(), file.ContentType));
|
||||
var result = _templateService.Post(requestData, new BucketFile(siteId, file.FileName, ms.ToArray(), file.ContentType));
|
||||
|
||||
return result.ToActionResult();
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
///
|
||||
/// </summary>
|
||||
/// <param name="siteId"></param>
|
||||
/// <param name="fileId"></param>
|
||||
/// <returns></returns>
|
||||
[HttpGet("{siteId}/{fileId}")]
|
||||
public IActionResult Get([FromRoute] Guid siteId, [FromRoute] Guid fileId) {
|
||||
return BadRequest();
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
///
|
||||
/// </summary>
|
||||
/// <param name="siteId"></param>
|
||||
/// <param name="fileId"></param>
|
||||
/// <param name="requestData"></param>
|
||||
/// <param name="file"></param>
|
||||
/// <returns></returns>
|
||||
[HttpGet("{siteId}/{fileId}")]
|
||||
public IActionResult Put([FromRoute] Guid siteId, [FromRoute] Guid fileId, [ModelBinder(typeof(JsonModelBinder<PostTemplateRequestModel>))] PostTemplateRequestModel requestData, IFormFile file) {
|
||||
return BadRequest();
|
||||
}
|
||||
|
||||
|
||||
/// <summary>
|
||||
/// Delete template
|
||||
|
||||
@ -12,12 +12,12 @@ namespace WeatherForecast.Models.Account.Requests {
|
||||
/// <summary>
|
||||
///
|
||||
/// </summary>
|
||||
public string? OldPassword { get; set; }
|
||||
public string OldPassword { get; set; } = string.Empty;
|
||||
|
||||
/// <summary>
|
||||
///
|
||||
/// </summary>
|
||||
public string? NewPassword { get; set; }
|
||||
public string NewPassword { get; set; } = string.Empty;
|
||||
|
||||
/// <summary>
|
||||
///
|
||||
|
||||
@ -0,0 +1,33 @@
|
||||
using System.Text.Json.Serialization;
|
||||
using System.ComponentModel.DataAnnotations;
|
||||
|
||||
using Core.Abstractions.Models;
|
||||
using Core.Converters;
|
||||
using Core.Enumerations;
|
||||
|
||||
namespace WeatherForecast.Models.Template.Requests {
|
||||
|
||||
/// <summary>
|
||||
///
|
||||
/// </summary>
|
||||
public class PostTemplateRequestModel : RequestModelBase {
|
||||
|
||||
/// <summary>
|
||||
///
|
||||
/// </summary>
|
||||
[JsonConverter(typeof(EnumerationDisplayNameConverter<TemplateTypes>))]
|
||||
public TemplateTypes TemplateType { get; set; } = TemplateTypes.Unknown;
|
||||
|
||||
/// <summary>
|
||||
///
|
||||
/// </summary>
|
||||
/// <param name="validationContext"></param>
|
||||
/// <returns></returns>
|
||||
/// <exception cref="NotImplementedException"></exception>
|
||||
public override IEnumerable<ValidationResult> Validate(ValidationContext validationContext) {
|
||||
|
||||
if (TemplateType == TemplateTypes.Unknown)
|
||||
yield return new ValidationResult($"{Errors.UnableToParse.Name} {nameof(TemplateType)}");
|
||||
}
|
||||
}
|
||||
}
|
||||
@ -44,10 +44,9 @@ namespace WeatherForecast.Services {
|
||||
///
|
||||
/// </summary>
|
||||
/// <param name="user"></param>
|
||||
/// <param name="oldPassword"></param>
|
||||
/// <param name="newPassword"></param>
|
||||
/// <param name="requestData"></param>
|
||||
/// <returns></returns>
|
||||
(Guid?, IDomainResult) PasswordChange(User user, string oldPassword, string newPassword);
|
||||
(Guid?, IDomainResult) PasswordChange(User user, PasswordChangeRequestModel requestData);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
@ -164,7 +163,12 @@ namespace WeatherForecast.Services {
|
||||
|
||||
var htmlBody = Encoding.UTF8.GetString(template.Bytes);
|
||||
|
||||
htmlBody = htmlBody.Replace("{{token}}", user.Authentication.PasswordRecovery());
|
||||
htmlBody = htmlBody
|
||||
.Replace("{{subject}}", site.PassworRecoverySettings.Subject)
|
||||
.Replace("{{userName}}", user.Username)
|
||||
.Replace("{{passwordRecoveryToken}}", user.Authentication.PasswordRecovery())
|
||||
.Replace("{{poweredBy.target}}", site.PoweredBy.Target)
|
||||
.Replace("{{poweredBy.anchorText}}", site.PoweredBy.AnchorText);
|
||||
|
||||
_userDataProvider.Update(user);
|
||||
|
||||
@ -210,12 +214,11 @@ namespace WeatherForecast.Services {
|
||||
///
|
||||
/// </summary>
|
||||
/// <param name="user"></param>
|
||||
/// <param name="oldPassword"></param>
|
||||
/// <param name="newPassword"></param>
|
||||
/// <param name="requestData"></param>
|
||||
/// <returns></returns>
|
||||
public (Guid?, IDomainResult) PasswordChange(User user, string oldPassword, string newPassword) {
|
||||
public (Guid?, IDomainResult) PasswordChange(User user, PasswordChangeRequestModel requestData) {
|
||||
|
||||
user.Authentication.PasswordChange(oldPassword, newPassword);
|
||||
user.Authentication.PasswordChange(requestData.OldPassword, requestData.NewPassword);
|
||||
|
||||
var (userId, userUpdateResult) = _userDataProvider.Update(user);
|
||||
if (!userUpdateResult.IsSuccess || userId == null)
|
||||
|
||||
@ -1,7 +1,11 @@
|
||||
using Core.Abstractions;
|
||||
using DataProviders.Buckets;
|
||||
|
||||
using DomainResults.Common;
|
||||
|
||||
using DataProviders.Buckets;
|
||||
|
||||
using WeatherForecast.Models.Template.Requests;
|
||||
|
||||
namespace WeatherForecast.Services;
|
||||
|
||||
/// <summary>
|
||||
@ -12,9 +16,10 @@ public interface ITemplateService {
|
||||
/// <summary>
|
||||
///
|
||||
/// </summary>
|
||||
/// <param name="requestData"></param>
|
||||
/// <param name="file"></param>
|
||||
/// <returns></returns>
|
||||
(Guid?, IDomainResult) Post(BucketFile file);
|
||||
(Guid?, IDomainResult) Post(PostTemplateRequestModel requestData, BucketFile file);
|
||||
|
||||
/// <summary>
|
||||
///
|
||||
@ -48,9 +53,10 @@ public class TemplateService : ServiceBase<TemplateService>, ITemplateService {
|
||||
/// <summary>
|
||||
///
|
||||
/// </summary>
|
||||
/// <param name="requestData"></param>
|
||||
/// <param name="file"></param>
|
||||
/// <returns></returns>
|
||||
public (Guid?, IDomainResult) Post(BucketFile file) {
|
||||
public (Guid?, IDomainResult) Post(PostTemplateRequestModel requestData, BucketFile file) {
|
||||
|
||||
var (fileId, uploadFileResult) = _templateBucketDataProvider.Upload(file);
|
||||
if (!uploadFileResult.IsSuccess || fileId == null)
|
||||
|
||||
@ -3,4 +3,4 @@ WiredTiger 10.0.2: (December 21, 2021)
|
||||
WiredTiger version
|
||||
major=10,minor=0,patch=2
|
||||
file:WiredTiger.wt
|
||||
access_pattern_hint=none,allocation_size=4KB,app_metadata=,assert=(commit_timestamp=none,durable_timestamp=none,read_timestamp=none,write_timestamp=off),block_allocation=best,block_compressor=,cache_resident=false,checksum=on,collator=,columns=,dictionary=0,encryption=(keyid=,name=),format=btree,huffman_key=,huffman_value=,id=0,ignore_in_memory_cache_size=false,internal_item_max=0,internal_key_max=0,internal_key_truncate=true,internal_page_max=4KB,key_format=S,key_gap=10,leaf_item_max=0,leaf_key_max=0,leaf_page_max=32KB,leaf_value_max=0,log=(enabled=true),memory_page_image_max=0,memory_page_max=5MB,os_cache_dirty_max=0,os_cache_max=0,prefix_compression=false,prefix_compression_min=4,readonly=false,split_deepen_min_child=0,split_deepen_per_child=0,split_pct=90,tiered_object=false,tiered_storage=(auth_token=,bucket=,bucket_prefix=,cache_directory=,local_retention=300,name=,object_target_size=0),value_format=S,verbose=[],version=(major=1,minor=1),write_timestamp_usage=none,checkpoint=(WiredTigerCheckpoint.99539=(addr="018081e4760268df8181e4e314ef2b8281e4f0cb9f65808080e301ffc0e3010fc0",order=99539,time=1674424019,size=81920,newest_start_durable_ts=0,oldest_start_ts=0,newest_txn=3110,newest_stop_durable_ts=0,newest_stop_ts=-1,newest_stop_txn=-11,prepare=0,write_gen=299042,run_write_gen=294763)),checkpoint_backup_info=,checkpoint_lsn=(38,1188480)
|
||||
access_pattern_hint=none,allocation_size=4KB,app_metadata=,assert=(commit_timestamp=none,durable_timestamp=none,read_timestamp=none,write_timestamp=off),block_allocation=best,block_compressor=,cache_resident=false,checksum=on,collator=,columns=,dictionary=0,encryption=(keyid=,name=),format=btree,huffman_key=,huffman_value=,id=0,ignore_in_memory_cache_size=false,internal_item_max=0,internal_key_max=0,internal_key_truncate=true,internal_page_max=4KB,key_format=S,key_gap=10,leaf_item_max=0,leaf_key_max=0,leaf_page_max=32KB,leaf_value_max=0,log=(enabled=true),memory_page_image_max=0,memory_page_max=5MB,os_cache_dirty_max=0,os_cache_max=0,prefix_compression=false,prefix_compression_min=4,readonly=false,split_deepen_min_child=0,split_deepen_per_child=0,split_pct=90,tiered_object=false,tiered_storage=(auth_token=,bucket=,bucket_prefix=,cache_directory=,local_retention=300,name=,object_target_size=0),value_format=S,verbose=[],version=(major=1,minor=1),write_timestamp_usage=none,checkpoint=(WiredTigerCheckpoint.101439=(addr="019181e43493ac119281e4c3feb5439381e4de446160808080e301ffc0e3010fc0",order=101439,time=1675717350,size=81920,newest_start_durable_ts=0,oldest_start_ts=0,newest_txn=462,newest_stop_durable_ts=0,newest_stop_ts=-1,newest_stop_txn=-11,prepare=0,write_gen=304767,run_write_gen=304099)),checkpoint_backup_info=,checkpoint_lsn=(40,185856)
|
||||
|
||||
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Loading…
Reference in New Issue
Block a user