رمزنگاری و رمزگشایی خودکار خواص مدلها در ASP.NET Core
نویسنده: وحید نصیری
تاریخ: ۱۳۹۹/۰۷/۰۷ ۱۳:۲۵
آدرس: www.dntips.ir
| مطالب | ۳۶۹۴ |
| نویسندگان | ۲۷۶ |
| گروههای مطالب | ۱۰۲۴ |
| نقشههای راه | ۱۱۹ |
| دورهها | ۱۴ |
| اشتراکها | ۱۷۹۱۴ |
using System;
namespace EncryptedModelBinder.Utils
{
[AttributeUsage(AttributeTargets.Property, AllowMultiple = false)]
public class EncryptedFieldAttribute : Attribute { }
} namespace EncryptedModelBinder.Utils
{
public class EncryptedFieldResultFilter : ResultFilterAttribute
{
private readonly IProtectionProviderService _protectionProviderService;
private readonly ILogger<EncryptedFieldResultFilter> _logger;
private readonly ConcurrentDictionary<Type, bool> _modelsWithEncryptedFieldAttributes = new ConcurrentDictionary<Type, bool>();
public EncryptedFieldResultFilter(
IProtectionProviderService protectionProviderService,
ILogger<EncryptedFieldResultFilter> logger)
{
_protectionProviderService = protectionProviderService;
_logger = logger;
}
public override void OnResultExecuting(ResultExecutingContext context)
{
var model = context.Result switch
{
PageResult pageResult => pageResult.Model, // For Razor pages
ViewResult viewResult => viewResult.Model, // For MVC Views
ObjectResult objectResult => objectResult.Value, // For Web API results
_ => null
};
if (model is null)
{
return;
}
if (typeof(IEnumerable).IsAssignableFrom(model.GetType()))
{
foreach (var item in model as IEnumerable)
{
encryptProperties(item);
}
}
else
{
encryptProperties(model);
}
}
private void encryptProperties(object model)
{
var modelType = model.GetType();
if (_modelsWithEncryptedFieldAttributes.TryGetValue(modelType, out var hasEncryptedFieldAttribute)
&& !hasEncryptedFieldAttribute)
{
return;
}
foreach (var property in modelType.GetProperties())
{
var attribute = property.GetCustomAttributes(typeof(EncryptedFieldAttribute), false).FirstOrDefault();
if (attribute == null)
{
continue;
}
hasEncryptedFieldAttribute = true;
var value = property.GetValue(model);
if (value is null)
{
continue;
}
if (value.GetType() != typeof(string))
{
_logger.LogWarning($"[EncryptedField] should be applied to `string` proprties, But type of `{property.DeclaringType}.{property.Name}` is `{property.PropertyType}`.");
continue;
}
var encryptedData = _protectionProviderService.Encrypt(value.ToString());
property.SetValue(model, encryptedData);
}
_modelsWithEncryptedFieldAttributes.TryAdd(modelType, hasEncryptedFieldAttribute);
}
}
} namespace EncryptedModelBinder
{
public class Startup
{
public void ConfigureServices(IServiceCollection services)
{
services.AddDNTCommonWeb();
services.AddControllersWithViews(options =>
{
options.Filters.Add(typeof(EncryptedFieldResultFilter));
});
} namespace EncryptedModelBinder.Utils
{
public class EncryptedFieldModelBinderProvider : IModelBinderProvider
{
public IModelBinder GetBinder(ModelBinderProviderContext context)
{
if (context == null)
{
throw new ArgumentNullException(nameof(context));
}
if (context.Metadata.IsComplexType)
{
return null;
}
var propName = context.Metadata.PropertyName;
if (string.IsNullOrWhiteSpace(propName))
{
return null;
}
var propInfo = context.Metadata.ContainerType.GetProperty(propName);
if (propInfo == null)
{
return null;
}
var attribute = propInfo.GetCustomAttributes(typeof(EncryptedFieldAttribute), false).FirstOrDefault();
if (attribute == null)
{
return null;
}
return new BinderTypeModelBinder(typeof(EncryptedFieldModelBinder));
}
}
} namespace EncryptedModelBinder.Utils
{
public class EncryptedFieldModelBinder : IModelBinder
{
private readonly IProtectionProviderService _protectionProviderService;
public EncryptedFieldModelBinder(IProtectionProviderService protectionProviderService)
{
_protectionProviderService = protectionProviderService;
}
public Task BindModelAsync(ModelBindingContext bindingContext)
{
if (bindingContext == null)
{
throw new ArgumentNullException(nameof(bindingContext));
}
var logger = bindingContext.HttpContext.RequestServices.GetRequiredService<ILoggerFactory>();
var fallbackBinder = new SimpleTypeModelBinder(bindingContext.ModelType, logger);
var valueProviderResult = bindingContext.ValueProvider.GetValue(bindingContext.ModelName);
if (valueProviderResult == ValueProviderResult.None)
{
return fallbackBinder.BindModelAsync(bindingContext);
}
bindingContext.ModelState.SetModelValue(bindingContext.ModelName, valueProviderResult);
var valueAsString = valueProviderResult.FirstValue;
if (string.IsNullOrWhiteSpace(valueAsString))
{
return fallbackBinder.BindModelAsync(bindingContext);
}
var decryptedResult = _protectionProviderService.Decrypt(valueAsString);
bindingContext.Result = ModelBindingResult.Success(decryptedResult);
return Task.CompletedTask;
}
}
} namespace EncryptedModelBinder
{
public class Startup
{
public void ConfigureServices(IServiceCollection services)
{
services.AddDNTCommonWeb();
services.AddControllersWithViews(options =>
{
options.ModelBinderProviders.Insert(0, new EncryptedFieldModelBinderProvider());
options.Filters.Add(typeof(EncryptedFieldResultFilter));
});
} namespace EncryptedModelBinder.Models
{
public class ProductInputModel
{
[EncryptedField]
public string Id { get; set; }
[EncryptedField]
public int Price { get; set; }
public string Name { get; set; }
}
}
namespace EncryptedModelBinder.Models
{
public class ProductViewModel
{
[EncryptedField]
public string Id { get; set; }
[EncryptedField]
public int Price { get; set; }
public string Name { get; set; }
}
} namespace EncryptedModelBinder.Controllers
{
public class HomeController : Controller
{
public IActionResult Index()
{
var model = getProducts();
return View(model);
}
public ActionResult<string> Details(ProductInputModel model)
{
return model.Id;
}
public ActionResult<List<ProductViewModel>> Products()
{
return getProducts();
}
private static List<ProductViewModel> getProducts()
{
return new List<ProductViewModel>
{
new ProductViewModel { Id = "1", Name = "Product 1"},
new ProductViewModel { Id = "2", Name = "Product 2"},
new ProductViewModel { Id = "3", Name = "Product 3"}
};
}
}
} @model List<ProductViewModel>
<h3>Home</h3>
<ul>
@foreach (var item in Model)
{
<li><a asp-action="Details" asp-route-id="@item.Id">@item.Name</a></li>
}
</ul>
namespace EncryptedModelBinder.Utils
{
public class EncryptedFieldResultFilter : ResultFilterAttribute
{
// ...
public override void OnResultExecuting(ResultExecutingContext context)
{
var model = context.Result switch
{
PageResult pageResult => pageResult.Model, // For Razor pages
ViewResult viewResult => viewResult.Model, // For MVC Views
PartialViewResult partialViewResult => partialViewResult.Model, // For `return PartialView`
ObjectResult objectResult => objectResult.Value, // For Web API results
_ => null
};