Just going to lay out all the info i have:
In short, I am looking for something exactly (literally) like this but compatible with ASP Core (2.2) and the C# MongoDB Driver (2.7).
This seems like such a common requirement, I am very surprised i can't find anything already built.
Here is what i have so far:
Model:
public class Patient
{
//comes from the client as XXXXXXXXX, RegEx: "([0-9]{9})"
//[MongoEncrypt]
public EncryptedString SocialSecurityNumber { get; set; }
}
Attribute:
[AttributeUsage(AttributeTargets.Property)]
public class MongoEncryptAttribute : BsonSerializerAttribute
{
public MongoEncryptAttribute()
{
SerializerType = typeof(MongoEncryptSerializer);
}
}
Custom Serializer:
public interface IMongoEncryptSerializer : IBsonSerializer<EncryptedString>{ }
public class MongoEncryptSerializer : SerializerBase<EncryptedString>, IMongoEncryptSerializer
{
private readonly string _encryptionKey;
public MongoEncryptSerializer(IConfiguration configuration)
{
_encryptionKey = configuration.GetSection("MongoDb")["EncryptionKey"];
}
public override EncryptedString Deserialize(BsonDeserializationContext context, BsonDeserializationArgs args)
{
var encryptedString = context.Reader.ReadString();
return AesThenHmac.SimpleDecryptWithPassword(encryptedString, _encryptionKey);
}
public override void Serialize(BsonSerializationContext context, BsonSerializationArgs args, EncryptedString value)
{
var encryptedString = AesThenHmac.SimpleEncryptWithPassword(value, _encryptionKey);
context.Writer.WriteString(encryptedString);
}
}
Open Items:
Use DI (vanilla .net core DI) to get the Serializer. thinking of something like BsonSerializer.RegisterSerializer(type,serializer) in a bootstrap method where i can access the service collection and do a GetInstance but then i would need string SocialSecurityNumber to use a custom type (maybe SecureString?)
Went with a custom type,EncryptedString, with implicit string conversion
Use DI in the serializer to get the key (initially from IConfiguration/appsettings.json and then ultimately from Azure KeyVault (whole new can of worms for me)) and the EncryptionProvider
deterministic encryption for searching. AesThenHmac comes from this popular post. I can store and retrieve data back fine in its current implementation. But in order to search for SSNs, I need deterministic encryption which this lib does not provide.
My Solution:
Model:
public class Patient
{
//comes from the client as XXXXXXXXX, RegEx: "([0-9]{9})"
public EncryptedString SocialSecurityNumber { get; set; }
}
Custom Type:
public class EncryptedString
{
private readonly string _value;
public EncryptedString(string value)
{
_value = value;
}
public static implicit operator string(EncryptedString s)
{
return s._value;
}
public static implicit operator EncryptedString(string value)
{
if (value == null)
return null;
return new EncryptedString(value);
}
}
Serializer(using Deterministic Encryption):
public interface IEncryptedStringSerializer : IBsonSerializer<EncryptedString> {}
public class EncryptedStringSerializer : SerializerBase<EncryptedString>, IEncryptedStringSerializer
{
private readonly IDeterministicEncrypter _encrypter;
private readonly string _encryptionKey;
public EncryptedStringSerializer(IConfiguration configuration, IDeterministicEncrypter encrypter)
{
_encrypter = encrypter;
_encryptionKey = configuration.GetSection("MongoDb")["EncryptionKey"];
}
public override EncryptedString Deserialize(BsonDeserializationContext context, BsonDeserializationArgs args)
{
var encryptedString = context.Reader.ReadString();
return _encrypter.DecryptStringWithPassword(encryptedString, _encryptionKey);
}
public override void Serialize(BsonSerializationContext context, BsonSerializationArgs args, EncryptedString value)
{
var encryptedString = _encrypter.EncryptStringWithPassword(value, _encryptionKey);
context.Writer.WriteString(encryptedString);
}
}
Registering the serializer:
collection.AddScoped<IEncryptedStringSerializer, EncryptedStringSerializer>();
//then later...
BsonSerializer.RegisterSerializer<EncryptedString>(sp.GetService<IEncryptedStringSerializer>());
Related
I have an API with multiple endpoints. I'd like to add a property to all endpoint responses, without adding it to each endpoint response model individually.
Ex:
public class MyClass
{
public string MyProperty { get; set; } = "Hello";
}
public class MyOtherClass
{
public string MyOtherProperty { get; set; } = "World";
}
public class MyController : ControllerBase
{
[HttpPost]
public async Task<ActionResult<MyClass>> EndpointOne(POSTData data)
{
// implementation omitted
}
[HttpPost]
public async Task<ActionResult<MyOtherClass>> EndpointTwo(POSTOtherData otherData)
{
// implementation omitted
}
}
Calling either endpoint returns a JSON representation of MyClass or MyOtherClass as appropriate - i.e.
{ "MyProperty":"Hello" } or { "MyOtherProperty":"World" }
I want to add a property, say a string ApiName, to all endpoints in the API, so that the result of the above code would be either (as appropriate)
{ "MyProperty":"Hello", "ApiName":"My awesome API" }
or
{ "MyOtherProperty":"World", "ApiName":"My awesome API" }
Is there a way to hook into the JSON-stringified result just before returning and add a top-level property like that? If so, I presume I'd have to wire it up in startup.cs, so I've been looking at app.UseEndpoints(...) methods, but haven't found anything that's worked so far. Either it's not added the property, or it's replaced the original result with the new property.
Thanks in advance!
Use Newtonsoft.Json in your net web api
Register a custom contract resolver in Startup.cs:
builder.Services.AddControllers()
.AddNewtonsoftJson(options => options.SerializerSettings.ContractResolver = CustomContractResolver.Instance);
The implementation:
public class CustomContractResolver : DefaultContractResolver {
public static CustomContractResolver Instance { get; } = new CustomContractResolver();
protected override IList<JsonProperty> CreateProperties(Type type, MemberSerialization memberSerialization)
{
var properties = base.CreateProperties(type, memberSerialization);
// add new property
...
properties.Add(newProp);
return properties;
}}
See more Json.net Add property to every class containing of a certain type
You can add a base class with the shared property. Should work for both XML and JSON.
public class MyApiClass
{
public string ApiName => "MyAwesomeApi";
}
public class MyClass : MyApiClass
{
public string MyProperty { get; set; } = "Hello";
}
public class MyOtherClass : MyApiClass
{
public string MyOtherProperty { get; set; } = "World";
}
public class MyController : ControllerBase
{
[HttpPost]
public async Task<ActionResult<MyClass>> EndpointOne(POSTData data)
{
// implementation omitted
}
[HttpPost]
public async Task<ActionResult<MyOtherClass>> EndpointTwo(POSTOtherData otherData)
{
// implementation omitted
}
}
My 0.02 cents says to implement an abstract base class.
Abstract class inheritance look similar to a standard inheritance.
public class MyClass:MyAbstractClass
{
[JsonPropertyName("Class Property")]
public string MyProperty { get; set; } = "Hello";
}
public class MyOtherClass:MyAbstractClass
{
[JsonPropertyName("Class Property")]
public string MyOtherProperty { get; set; } = "World";
}
However the abstract class will allow you to implement additional features in the event you need them in the future.
public abstract class MyAbstractClass{
[JsonPropertyName("API Name")]
public string ApiName{get;set;}="My Aweomse API";
//Just a thought if you want to keep track of the end point names
//while keeping your object names the same
[JsonIgnore(Condition = JsonIgnoreCondition.Always)]
public string EndPointName{
get{
return get_endpoint_name();
}}
private string get_endpoint_name(){
return this.GetType().Name;
}
//May as well make it easy to grab the JSON
[JsonIgnore(Condition = JsonIgnoreCondition.Always)]
public string As_JSON{
get {
return to_json();
}}
private string to_json(){
object _myObject = this;
string _out;
JsonSerializerOptions options =
new JsonSerializerOptions {
WriteIndented = true };
_out =
JsonSerializer.Serialize(_myObject, options);
return _out;
}
}
Probably should have implemented a generic return object, then you could just loop through the task results. I suppose you still can if you have the task return only the JSON string.
public static void run(){
Task<MyClass> _t0 = task0();
Task<MyOtherClass> _t1 = task1();
Task[] _tasks = new Task[]{_t0,_t1};
Task.WhenAll(_tasks).Wait();
Console.WriteLine(""
+$"{_t1.Result.ApiName}:\n"
+$"End Point: {_t1.Result.EndPointName}:\n"
+$"JSON:\n{_t1.Result.As_JSON}");
Console.WriteLine(""
+$"{_t0.Result.ApiName}:\n"
+$"End Point: {_t0.Result.EndPointName}:\n"
+$"JSON:\n{_t0.Result.As_JSON}");
}
private static Task<MyClass> task0(){
return Task.Run(()=>{
Console.WriteLine("Task 0 Doing Something");
return new MyClass();
});
}
private static Task<MyOtherClass> task1(){
return Task.Run(()=>{
Console.WriteLine("Task 1 Doing Something");
return new MyOtherClass();
});
}
And of course the aweosome...awesome:-) results:
Another thought is that you could implement your two different tasks as abstract methods, but that's a different conversation all together.
In addition to all of the great answers, I prefer to use Action Filter and ExpandoObject.
In Program File you should add your custom action Filter.
builder.Services.AddControllers(opt =>
{
opt.Filters.Add<ResponseHandler>();
});
and ResponseHandler acts like below:
public class ResponseHandler : IActionFilter
{
public void OnActionExecuted(ActionExecutedContext context)
{
IDictionary<string, object> expando = new ExpandoObject();
foreach (var propertyInfo in (context.Result as ObjectResult).Value.GetType().GetProperties())
{
var currentValue = propertyInfo.GetValue((context.Result as ObjectResult).Value);
expando.Add(propertyInfo.Name, currentValue);
}
dynamic result = expando as ExpandoObject;
result.ApiName = context.ActionDescriptor.RouteValues["action"].ToString();
context.Result = new ObjectResult(result);
}
public void OnActionExecuting(ActionExecutingContext context)
{
}
}
There are few methods which have Application.Current.Properties and Application.Current.SavePropertiesAsync methods.
So how do I test methods having these two in them? I'm stuck after trying to use Unity container for them but its only working for Properties not SavePropertiesAsync.
How can I implement it?
I have implemented it as:
public interface IAppProperties { IDictionary<string, object> Properties { get; set; } }
public class AppProperty:IAppProperties
{
public const string AppPropertiesName = "AppProperties";
public IDictionary<string, object> Properties { get; set; }
public AppProperty(IDictionary<string, object> appProperties)
{
Properties = appProperties;
}
}
In App XAML.cs
UnityContainer container = new UnityContainer();
if (!IsUnitTestCase)
{
container.RegisterInstance<IDictionary<string, object>>(AppProperty.AppPropertiesName, Application.Current.Properties);
}
else
{
container.RegisterInstance<IDictionary<string, object>>(AppProperty.AppPropertiesName, new Dictionary<string,object>());
}
container.RegisterType<IAppProperties,AppProperty>();
Application.Current.Resources.Add("Unity", container);
If a class depends directly on Application.Current then you can't test it. But it looks like you're already on track with depending on an abstraction.
Suppose there are three things you need to be able to do:
Retrieve a property
Set a property
Save all properties
You can define an abstraction that represents those behaviors:
public interface IApplicationProperties
{
object GetProperty(string key);
void SetProperty(string key, object value);
Task SavePropertiesAsync();
}
Your default implementation could look like this (although there's plenty of room for improvement.)
public class ApplicationProperties : IApplicationProperties
{
private readonly Application _application;
public ApplicationProperties(Application application)
{
_application = application;
}
public object GetProperty(string key)
{
// or whatever behavior you want when the key is missing
return _application.Properties.TryGetValue(key, out object result) ? result : null;
}
public void SetProperty(string key, object value)
{
_application.Properties[key] = value;
}
public async Task SavePropertiesAsync()
{
await _application.SavePropertiesAsync();
}
}
This class could either depend on Application.Current or you could inject the Application into it.
This could benefit from better type checking and perhaps limiting/defining what settings can be read and set. But it allows you to both access the behaviors of Application through an abstraction while mocking the abstraction for unit tests. You could use Moq or just write a simple test double to use in tests.
Here's a tweak to the approach that includes a test double:
// base class
public abstract class ApplicationPropertiesBase : IApplicationProperties
{
protected abstract IDictionary<string, object> Properties { get; }
public object GetProperty(string key)
{
return Properties.TryGetValue(key, out object result) ? result : null;
}
public void SetProperty(string key, object value)
{
Properties[key] = value;
}
public abstract Task SavePropertiesAsync();
}
// inject this
public class ApplicationProperties : ApplicationPropertiesBase
{
private readonly Application _application;
public ApplicationProperties(Application application)
{
_application = application;
}
protected override IDictionary<string, object> Properties => _application.Properties;
public override async Task SavePropertiesAsync()
{
await _application.SavePropertiesAsync();
}
}
// use for tests
public class ApplicationPropertiesTestDouble : ApplicationPropertiesBase
{
private readonly IDictionary<string, object> properties =
new Dictionary<string, object>();
protected override IDictionary<string, object> Properties => properties;
public override async Task SavePropertiesAsync()
{ }
}
I trying to implement localization in .net core 1.0 application using IStringLocalizer. I am able to do the localization for the view for which I have written something like this
private readonly IStringLocalizer<AboutController> _localizer;
public AboutController(IStringLocalizer<AboutController> localizer)
{
_localizer = localizer;
}
public IActionResult About()
{
ViewBag.Name = _localizer["Name"];
Return View();
}
So this is working fine, however I am curious how can I use IStringLocalizer in CustomAttribute from where I will be getting localized validation message.
Model
public partial class LMS_User
{
[RequiredFieldValidator("FirstNameRequired")]
public string FirstName { get; set; }
[RequiredFieldValidator("LastNameRequired")]
public string LastName { get; set; }
}
from model I have passed the resource key to custom attribute where I will be retrieving the localized message.
Custom Attribute
public class RequiredFieldValidator: ValidationAttribute , IClientModelValidator
{
private readonly string resourcekey = string.Empty;
public RequiredFieldValidator(string resourceID)
{
resourcekey = resourceID;
}
}
public void AddValidation(ClientModelValidationContext context)
{
if (context == null)
{
throw new ArgumentNullException(nameof(context));
}
// Here I want to get localized message using SQL.
var errorMessage = "This field is required field.";
MergeAttribute(context.Attributes, "data-val", "true");
MergeAttribute(context.Attributes, "data- val-Required",errorMessage);
}
private static bool MergeAttribute(IDictionary<string, string> attributes, string key, string value)
{
if (attributes.ContainsKey(key))
{
return false;
}
attributes.Add(key, value);
return true;
}
protected override ValidationResult IsValid(object value, ValidationContext validationContext)
{
return ValidationResult.Success;
}
So, how can I use IStringLocalizer in custom attribute ? I want to do this using SQL.
Any help on this appreaciated !
I like to implement localization as a service.
public RequiredFieldValidator(IStringLocalizer localizationService, string resourceID)
{
resourcekey = resourceID;
localization = localizationService;
}
public void AddValidation(ClientModelValidationContext context)
{
if (context == null)
{
throw new ArgumentNullException(nameof(context));
}
// Here I want to get localized message using SQL.
var errorMessage = lozalization["requiredFieldMessage"];
MergeAttribute(context.Attributes, "data-val", "true");
MergeAttribute(context.Attributes, "data- val-Required",errorMessage);
}
you can choose implement the interface using resource strings, accessing to database to get the translations,... Here i'm implementing one method accessing to resource strings, assuming that the resources are in the same project.
public class LocalizationService : IStringLocalizer {
public LocalizedString this[string name] {
return new LocalizedString(name, Properties.Resources.GetString(name));
}
//implement the rest of methods of IStringLocalizer
}
Summary: Im working with C# 4.5 version and more specifically in Web API.
Im trying to build an object and wrap it with attributes so when I receive a HTTP POST request, validation will be made in modelState.
a little example before code:
Lets say I have this following request object
public class PlayerRequest
{
[TeamId]
public string TeamId {set;get;}
[UserId]
public string UserId {set;get;}
}
now, I want to be able to just add an attribute to the class and it will check if class contains TeamId and UserId and if so, validate in db that in fact user has access to team.
so lets say, the declaration will be something like:
[PairsValidate]
public class TeamRequest
{
//...
}
What I aim to create is not a specific validation for TeamId and UserId but to create some sort of a pool of attribute pairs and run a simple loop to detect them and validate.
code so far:
[AttributeUsage(AttributeTargets.Class)]
public sealed class AccessValidator : ValidationAttribute
{
private readonly AttributePairValidator[] _validators =
{
UserIdTeamIdValidator.GetInstance(AccessManager.UserAccessToTeam)
};
public override bool IsValid(object value)
{
PropertyInfo[] properties = value.GetType().GetProperties();
foreach (PropertyInfo p in properties)
{
foreach (AttributePairValidator valPair in _validators)
{
valPair.Accept(/* here is the problem */ , p.GetValue as string);
}
}
}
}
public class AttributePairValidator
{
protected string fieldA;
protected string fieldB;
protected Func<string, string, Task<bool>> _validationMethod;
protected static object _lockObj = new object();
protected AttributePairValidator(Func<string, string, Task<bool>> validationMethod)
{
_validationMethod = validationMethod;
}
public bool Accept (ValidationAttribute attr, string val)
{
return true;
}
protected async Task<bool> Check()
{
if (!String.IsNullOrEmpty(fieldA) && !String.IsNullOrEmpty(fieldB))
return await _validationMethod(fieldA, fieldB);
return true;
}
}
public sealed class UserIdTeamIdValidator : AttributePairValidator
{
private static UserIdTeamIdValidator _instance = null;
private UserIdTeamIdValidator(Func<string, string, Task<bool>> validationMethod) : base (validationMethod)
{
}
public static UserIdTeamIdValidator GetInstance(Func<string, string, Task<bool>> validationMethod)
{
lock (_lockObj)
{
if (_instance == null)
_instance = new UserIdTeamIdValidator(validationMethod);
}
return _instance;
}
public async Task<bool> Accept(UserIdAttribute attr, string val)
{
fieldA = val;
return await Check();
}
public async Task<bool> Accept(TeamIdAttribute attr, string val)
{
fieldB = val;
return await Check();
}
}
other issue, if you guys already know how to solve it.
Im validating the request itself by headers and im storing some data in the actionContext's principal. In controllers i use: ActionContext.RequestContext.Principal.Identity.Name
is there any way to get this data when in validationAttribute scope?
Thanks.
We're currently refactoring our ASP.NET 4.0 Web Application to run on both plain old IIS and Azure. For the Settings (in the Properties namespace), I'd like to implement the State Pattern with an AzureSettingsState and a StandaloneSettingsState, which both provide settings getter methods.
Now could anybody help me figuring out how ASP.NET deserializes the non-String values (e.g. TimeSpan or StringCollection), so that I can deserialize them on my own in the context class? All settings seem to be strings there.
public abstract class ConfigStateBase
{
public abstract string GetSettingValue(string setting);
}
I've done something like this, still don't know if it works :D
public class AzureConfig:StandaloneConfig
{
protected override string GetAppSetting(string name)
{
return RoleEnvironment.GetConfigurationSettingValue(name);
}
protected override string GetConnectionString(string name)
{
return RoleEnvironment.GetConfigurationSettingValue(name);
}
}
public class StandaloneConfig
{
public IndexedSetting AppSettings { get; private set; }
public IndexedSetting ConnectionStrings { get; private set; }
public StandaloneConfig()
{
AppSettings = new IndexedSetting(GetAppSetting);
ConnectionStrings = new IndexedSetting(GetConnectionString);
}
protected virtual String GetAppSetting(String name)
{
return ConfigurationManager.AppSettings[name];
}
protected virtual String GetConnectionString(String name)
{
var cs = ConfigurationManager.ConnectionStrings[name];
if (cs != null)
return cs.ConnectionString;
else
return null;
}
public class IndexedSetting
{
Func<String, String> _getParameter;
public IndexedSetting(Func<String,String> getParameter)
{
_getParameter = getParameter;
}
public String this[String name]
{
get { return _getParameter(name); }
}
}
Don't forget to tell Azure to use it!
static AzureConfig _config = new AzureConfig();
void Application_Start(object sender, EventArgs e)
{
Microsoft.WindowsAzure.CloudStorageAccount.SetConfigurationSettingPublisher((configName, configSetter) =>
{
configSetter(_config.AppSettings[configName]);
});
}
If you use it let me know, I wanna get paid ... I mean I wanna know if it works :D