Command line Parser parses value eventhoug its not specified? - c#

I have begun starting using this https://github.com/commandlineparser/commandline
to pass the input parameters parsed to my application.
My problem here is that the input parameter passed is not required, meaning you can start the application without speciying them.
I have so far defined my command line options as such
public class CommandLineOptions
{
[Option(longName: "client-id", Required = false, HelpText = "Id of the client")]
public string ClientId { get; set; }
[Option(longName: "pw", Required = false, HelpText = "pw.")]
public string Password{ get; set; }
}
and in my main I parse them like this
Access access= Parser.Default.ParseArguments<CommandLineOptions>(args)
.MapResult(parsedFunc: (CommandLineOptions opts) => new Access(opts.ClientId, opts.Password),
notParsedFunc: (IEnumerable<Error> a) => new Access());
I want to use the parsedfunc: in case it is specified, and notParsedFunc: in case it is not specified.
but this always triggers the parsedFunc and since the value of both parameters are null, my inner method fails?
I've also tried changing the option to not required, this then throws an error in the console window that these parameter has not been specified, but triggers the correct method.

From the documentation :
If parsing succeeds, you'll get a derived Parsed type that exposes an instance of T through its Value property.
If parsing fails, you'll get a derived NotParsed type with errors present in Errors sequence.
NotParsed is called when the parsing fail, but in you case the parsing success because empty password is allowed.
You need use Parsed and check manually if the argument is present :
Access access = Parser.Default.ParseArguments<CommandLineOptions>(args)
.MapResult(
opts => opts.Password == null ? new Access() : new Access(opts.ClientId, opts.Password),
_ => null
);
if(access == null)
{
// Fail to create access
// Close withe exit code 1
Environment.Exit(1);
}

Related

Not able to access public property from ts file in Html file

I need to get some data from service and display it in HTML. I have put API call in service, and I got that data in ts file(checked in console), But When I am trying to get the same data into html, its showing null reference exception. Couldnt figure out what I missed.
export class SsoComponent implements OnInit {
public samlResponseData: SamlResponse;
constructor(
private ssoService: SsoService,
private store: Store<AppState>) { }
ngOnInit() {
this.verifySessionExpiration();
}
public verifySessionExpiration() {
this.store.pipe(select(getAuthData))
.subscribe(authData => {
if (authData) {
this.ssoService.fetchSamlResponse()
.subscribe(samlResponse => {
console.log(samlResponse);
this.samlResponseData = samlResponse;
});
} else {
this.ssoService.goLogin();
}
});
}
I am seeing the correct response in console. This is my code in HTML.
{{samlResponseData.ResponseData}}
I am getting a console error saying, "Unable to set property 'ResponseData' of undefined or null reference"
I have a model SamlResponse with a string property that I want to show it in HTML.
Please help.
The logic you have in verifySessionExpiration() asynchronous. You are getting that error because your template is trying to access samlResponseData before it has a value.
One way to fix it would be to only render the data when you know the value isn't null or undefined.
<ng-container *ngIf="samlResponseData">
{{samlResponseData.ResponseData}}
</ng-container>
Another option would be to initialize samlResponseData to some empty object:
samlResponseData = {};

Localization of RequiredAttribute in ASP.NET Core 2.0

I'm struggling with localization in my new .NET Core project.
I have 2 projects:
DataAccess project with Models and DataAnnotations (e.g. RequiredAttribute)
Web project with MVC views etc.
My wish is to localize all validation attributes globally in one single place to have the similar behavior like MVC 5. Is this possible?
I do not want to have separate language files for Models/Views etc.
Microsofts documentation is not very clear on using SharedResources.resx file with localized DataAnnotation messages.
In MVC 5 I didn't take care of it. I only needed to set the locale to my language and everything was fine.
I tried setting the ErrorMessageResourceName and ErrorMessageResourceType to my shared resource file name "Strings.resx" and "Strings.de.resx" in the DataAccess project:
[Required(ErrorMessageResourceName = "RequiredAttribute_ValidationError", ErrorMessageResourceType = typeof(Strings))]
I also tried the setting name to be RequiredAttribute_ValidationError - but it's not working.
I already added .AddDataAnnotationsLocalization() in Startup.cs - but it seems to do nothing.
I've read several articles but I couldn't find the cause why it's not working.
EDIT:
What I have so far:
1.) LocService class
public class LocService
{
private readonly IStringLocalizer _localizer;
public LocService(IStringLocalizerFactory factory)
{
_localizer = factory.Create(typeof(Strings));
}
public LocalizedString GetLocalizedHtmlString(string key)
{
return _localizer[key];
}
}
2.) Added Folder "Resources" with Strings.cs (empty class with dummy constructor)
3.) Added Strings.de-DE.resx file with one item "RequiredAttribute_ValidationError"
4.) Modified my Startup.cs
public void ConfigureServices(IServiceCollection services)
{
services.AddTransient<MessageService>();
services.AddDbContext<DataContext>(options =>
options.UseSqlServer(Configuration.GetConnectionString("DefaultConnection")));
services.AddSingleton<LocService>();
services.AddLocalization(options => options.ResourcesPath = "Resources");
services.AddMvc()
.AddJsonOptions(options => options.SerializerSettings.ContractResolver = new DefaultContractResolver())
.AddDataAnnotationsLocalization(
options =>
{
options.DataAnnotationLocalizerProvider = (type, factory) => factory.Create(typeof(Strings));
});
services.Configure<RequestLocalizationOptions>(
opts =>
{
var supportedCultures = new List<CultureInfo>
{
new CultureInfo("de-DE"),
};
opts.DefaultRequestCulture = new RequestCulture("de-DE");
// Formatting numbers, dates, etc.
opts.SupportedCultures = supportedCultures;
// UI strings that we have localized.
opts.SupportedUICultures = supportedCultures;
});
}
// This method gets called by the runtime. Use this method to configure the HTTP request pipeline.
public void Configure(IApplicationBuilder app, IHostingEnvironment env)
{
if (env.IsDevelopment())
{
app.UseBrowserLink();
app.UseDeveloperExceptionPage();
}
else
{
app.UseExceptionHandler("/Home/Error");
}
var locOptions = app.ApplicationServices.GetService<IOptions<RequestLocalizationOptions>>();
app.UseRequestLocalization(locOptions.Value);
app.UseStaticFiles();
app.UseMvcWithDefaultRoute();
}
I've followed the instructions here but it doesn't work:
https://damienbod.com/2017/11/01/shared-localization-in-asp-net-core-mvc/
Please keep in mind that my Models are kept in a separate project.
As #Sven points out in his comment to Tseng's answer it still requires that you specify an explicit ErrorMessage, which gets quite tedious.
The problem arises from the logic ValidationAttributeAdapter<TAttribute>.GetErrorMessage() uses to decide whether to use the provided IStringLocalizer or not.
I use the following solution to get around that issue:
Create a custom IValidationAttributeAdapterProvider implementation that uses the default ValidationAttributeAdapterProvider like this:
public class LocalizedValidationAttributeAdapterProvider : IValidationAttributeAdapterProvider
{
private readonly ValidationAttributeAdapterProvider _originalProvider = new ValidationAttributeAdapterProvider();
public IAttributeAdapter GetAttributeAdapter(ValidationAttribute attribute, IStringLocalizer stringLocalizer)
{
attribute.ErrorMessage = attribute.GetType().Name.Replace("Attribute", string.Empty);
if (attribute is DataTypeAttribute dataTypeAttribute)
attribute.ErrorMessage += "_" + dataTypeAttribute.DataType;
return _originalProvider.GetAttributeAdapter(attribute, stringLocalizer);
}
}
Register the adapter in Startup.ConfigureServices() Before calling AddMvc():
services.AddSingleton<Microsoft.AspNetCore.Mvc.DataAnnotations.IValidationAttributeAdapterProvider, LocalizedValidationAttributeAdapterProvider>();
I prefer to use "stricter" resource names based on the actual attributes, so the code above will look for resource names like "Required" and "DataType_Password", but this can of course be customized in many ways.
If you prefer resources names based on the default messages of the Attributes you could instead write something like:
attribute.ErrorMessage = attribute.FormatErrorMessage("{0}");
I tried setting the ErrorMessageResourceName and ErrorMessageResourceType to my shared resource file name "Strings.resx" and "Strings.de.resx" in the DataAccess project:
[Required(ErrorMessageResourceName = "RequiredAttribute_ValidationError", ErrorMessageResourceType = typeof(Strings))]
I also tried the setting name to be RequiredAttribute_ValidationError - but it's not working.
You were on the right track, but you don't necessarily need to set ErrorMessageResourceName / ErrorMessageResourceType properties.
Was we can see in the source code of ValidationAttributeAdapter<TAttribute>, the conditions to use the _stringLocalizer verison is when ErrorMessage is not null and ErrorMessageResourceName/ErrorMessageResourceType are null.
In other words, when you don't set any properties or only ErrorMessage. So a plain [Required] should just work (see source where is passed to the base classes constructor).
Now, when we look at the DataAnnotations resource file we see that the name is set to "RequiredAttribute_ValidationError" and the value to "The {0} field is required." which is the default English translation.
Now if you use "RequiredAttribute_ValidationError" with the German translation in your "Strings.de-DE.resx" (or just Strings.resx as fallback), it should work with the corrected namespace from the comments.
So using the above configuration and the strings from the GitHub repository you should be able to make the localization work without extra attributes.
It turned out that ValidationAttributeAdapterProvider approach doesn't work as it is meant to be used for "client side validation attributes" only (which doesn't make much sense to me because the attributes are specified on the server model).
But I found a solution that works to override all attributes with custom messages. It also is able to inject field name translations without spitting [Display] all over the place. It's convention-over-configuration in action.
Also, as a bonus, this solution overrides default model binding error texts that are used even before validation takes place. One caveat - if you receive JSON data, then Json.Net errors will be merged into ModelState errors and default binding errors won't be used. I haven't yet figured out how to prevent this from happening.
So, here are three classes you will need:
public class LocalizableValidationMetadataProvider : IValidationMetadataProvider
{
private IStringLocalizer _stringLocalizer;
private Type _injectableType;
public LocalizableValidationMetadataProvider(IStringLocalizer stringLocalizer, Type injectableType)
{
_stringLocalizer = stringLocalizer;
_injectableType = injectableType;
}
public void CreateValidationMetadata(ValidationMetadataProviderContext context)
{
// ignore non-properties and types that do not match some model base type
if (context.Key.ContainerType == null ||
!_injectableType.IsAssignableFrom(context.Key.ContainerType))
return;
// In the code below I assume that expected use of ErrorMessage will be:
// 1 - not set when it is ok to fill with the default translation from the resource file
// 2 - set to a specific key in the resources file to override my defaults
// 3 - never set to a final text value
var propertyName = context.Key.Name;
var modelName = context.Key.ContainerType.Name;
// sanity check
if (string.IsNullOrEmpty(propertyName) || string.IsNullOrEmpty(modelName))
return;
foreach (var attribute in context.ValidationMetadata.ValidatorMetadata)
{
var tAttr = attribute as ValidationAttribute;
if (tAttr != null)
{
// at first, assume the text to be generic error
var errorName = tAttr.GetType().Name;
var fallbackName = errorName + "_ValidationError";
// Will look for generic widely known resource keys like
// MaxLengthAttribute_ValidationError
// RangeAttribute_ValidationError
// EmailAddressAttribute_ValidationError
// RequiredAttribute_ValidationError
// etc.
// Treat errormessage as resource name, if it's set,
// otherwise assume default.
var name = tAttr.ErrorMessage ?? fallbackName;
// At first, attempt to retrieve model specific text
var localized = _stringLocalizer[name];
// Some attributes come with texts already preset (breaking the rule 3),
// even if we didn't do that explicitly on the attribute.
// For example [EmailAddress] has entire message already filled in by MVC.
// Therefore we first check if we could find the value by the given key;
// if not, then fall back to default name.
// Final attempt - default name from property alone
if (localized.ResourceNotFound) // missing key or prefilled text
localized = _stringLocalizer[fallbackName];
// If not found yet, then give up, leave initially determined name as it is
var text = localized.ResourceNotFound ? name : localized;
tAttr.ErrorMessage = text;
}
}
}
}
public class LocalizableInjectingDisplayNameProvider : IDisplayMetadataProvider
{
private IStringLocalizer _stringLocalizer;
private Type _injectableType;
public LocalizableInjectingDisplayNameProvider(IStringLocalizer stringLocalizer, Type injectableType)
{
_stringLocalizer = stringLocalizer;
_injectableType = injectableType;
}
public void CreateDisplayMetadata(DisplayMetadataProviderContext context)
{
// ignore non-properties and types that do not match some model base type
if (context.Key.ContainerType == null ||
!_injectableType.IsAssignableFrom(context.Key.ContainerType))
return;
// In the code below I assume that expected use of field name will be:
// 1 - [Display] or Name not set when it is ok to fill with the default translation from the resource file
// 2 - [Display(Name = x)]set to a specific key in the resources file to override my defaults
var propertyName = context.Key.Name;
var modelName = context.Key.ContainerType.Name;
// sanity check
if (string.IsNullOrEmpty(propertyName) || string.IsNullOrEmpty(modelName))
return;
var fallbackName = propertyName + "_FieldName";
// If explicit name is missing, will try to fall back to generic widely known field name,
// which should exist in resources (such as "Name_FieldName", "Id_FieldName", "Version_FieldName", "DateCreated_FieldName" ...)
var name = fallbackName;
// If Display attribute was given, use the last of it
// to extract the name to use as resource key
foreach (var attribute in context.PropertyAttributes)
{
var tAttr = attribute as DisplayAttribute;
if (tAttr != null)
{
// Treat Display.Name as resource name, if it's set,
// otherwise assume default.
name = tAttr.Name ?? fallbackName;
}
}
// At first, attempt to retrieve model specific text
var localized = _stringLocalizer[name];
// Final attempt - default name from property alone
if (localized.ResourceNotFound)
localized = _stringLocalizer[fallbackName];
// If not found yet, then give up, leave initially determined name as it is
var text = localized.ResourceNotFound ? name : localized;
context.DisplayMetadata.DisplayName = () => text;
}
}
public static class LocalizedModelBindingMessageExtensions
{
public static IMvcBuilder AddModelBindingMessagesLocalizer(this IMvcBuilder mvc,
IServiceCollection services, Type modelBaseType)
{
var factory = services.BuildServiceProvider().GetService<IStringLocalizerFactory>();
var VL = factory.Create(typeof(ValidationMessagesResource));
var DL = factory.Create(typeof(FieldNamesResource));
return mvc.AddMvcOptions(o =>
{
// for validation error messages
o.ModelMetadataDetailsProviders.Add(new LocalizableValidationMetadataProvider(VL, modelBaseType));
// for field names
o.ModelMetadataDetailsProviders.Add(new LocalizableInjectingDisplayNameProvider(DL, modelBaseType));
// does not work for JSON models - Json.Net throws its own error messages into ModelState :(
// ModelBindingMessageProvider is only for FromForm
// Json works for FromBody and needs a separate format interceptor
DefaultModelBindingMessageProvider provider = o.ModelBindingMessageProvider;
provider.SetValueIsInvalidAccessor((v) => VL["FormatHtmlGeneration_ValueIsInvalid", v]);
provider.SetAttemptedValueIsInvalidAccessor((v, x) => VL["FormatModelState_AttemptedValueIsInvalid", v, x]);
provider.SetMissingBindRequiredValueAccessor((v) => VL["FormatModelBinding_MissingBindRequiredMember", v]);
provider.SetMissingKeyOrValueAccessor(() => VL["FormatKeyValuePair_BothKeyAndValueMustBePresent" ]);
provider.SetMissingRequestBodyRequiredValueAccessor(() => VL["FormatModelBinding_MissingRequestBodyRequiredMember"]);
provider.SetNonPropertyAttemptedValueIsInvalidAccessor((v) => VL["FormatModelState_NonPropertyAttemptedValueIsInvalid", v]);
provider.SetNonPropertyUnknownValueIsInvalidAccessor(() => VL["FormatModelState_UnknownValueIsInvalid"]);
provider.SetUnknownValueIsInvalidAccessor((v) => VL["FormatModelState_NonPropertyUnknownValueIsInvalid", v]);
provider.SetValueMustNotBeNullAccessor((v) => VL["FormatModelBinding_NullValueNotValid", v]);
provider.SetValueMustBeANumberAccessor((v) => VL["FormatHtmlGeneration_ValueMustBeNumber", v]);
provider.SetNonPropertyValueMustBeANumberAccessor(() => VL["FormatHtmlGeneration_NonPropertyValueMustBeNumber"]);
});
}
}
In ConfigureServices in your Startup.cs file:
services.AddMvc( ... )
.AddModelBindingMessagesLocalizer(services, typeof(IDtoModel));
I have used my custom empty IDtoModel interface here and applied it to all my API models that will need the automatic localization for errors and field names.
Create a folder Resources and put empty classes ValidationMessagesResource and FieldNamesResource inside it.
Create ValidationMessagesResource.ab-CD.resx and FieldNamesResource .ab-CD.resx files (replace ab-CD with your desired culture).
Fill in the values for the keys you need, e.g. FormatModelBinding_MissingBindRequiredMember, MaxLengthAttribute_ValidationError ...
When launching the API from a browser, make sure to modify accept-languages header to be your culture name, otherwise Core will use it instead of defaults. For API that needs single language only, I prefer to disable culture providers altogether using the following code:
private readonly CultureInfo[] _supportedCultures = new[] {
new CultureInfo("ab-CD")
};
...
var ci = new CultureInfo("ab-CD");
// can customize decimal separator to match your needs - some customers require to go against culture defaults and, for example, use . instead of , as decimal separator or use different date format
/*
ci.NumberFormat.NumberDecimalSeparator = ".";
ci.NumberFormat.CurrencyDecimalSeparator = ".";
*/
_defaultRequestCulture = new RequestCulture(ci, ci);
...
services.Configure<RequestLocalizationOptions>(options =>
{
options.DefaultRequestCulture = _defaultRequestCulture;
options.SupportedCultures = _supportedCultures;
options.SupportedUICultures = _supportedCultures;
options.RequestCultureProviders = new List<IRequestCultureProvider>(); // empty list - use default value always
});
unfortunately, it is not that simple to localize all error messages for data attributes in one single place! because there are different types of error messages,
Error messages for standard data attributes:
[Required]
[Range]
[StringLength]
[Compare]
...etc.
Error messages for ModelBinding:
ValueIsInvalid
ValueMustNotBeNull
PropertyValueMustBeANumber
...etc.
and Identity error messages:
DuplicateEmail
DuplicateRoleName
InvalidUserName
PasswordRequiresLower
PasswordRequiresUpper
...etc
each must be configured in the startup file. Additionaly client side validation must be considered as well.
you may check these articles for more details, it contains live demo and sample project on GitHub:
Developing multicultural web application:
http://www.ziyad.info/en/articles/10-Developing_Multicultural_Web_Application
Localizing data annotations:
http://www.ziyad.info/en/articles/16-Localizing_DataAnnotations
Localizing ModelBinding error messages:
http://www.ziyad.info/en/articles/18-Localizing_ModelBinding_Error_Messages
Localizing identity error messages:
http://www.ziyad.info/en/articles/20-Localizing_Identity_Error_Messages
and client side validation:
http://ziyad.info/en/articles/19-Configuring_Client_Side_Validation
hope it helps :)
public class RequiredExAttribute : RequiredAttribute
{
public override string FormatErrorMessage(string name)
{
string Format = GetAFormatStringFromSomewhereAccordingToCurrentCulture();
return string.Format(Format, name);
}
}
...
public class MyModel
{
[RequiredEx]
public string Name { get; set; }
}

InstallerSetup.cs pass installation parameters in from1.cs

I have properly overwrite commit in InstallerSetup.cs I do not wish to write the user entered value to app.config but rather I want to pass the string Context.Parameters["TESTPARAMETER"]; to another class in form1.cs on load function. I tried string test = InstallerSetup.Context.Parameters["TESTPARAMETER"];
but getting InstallerSetup.Context is null. Please Help.
InstallerSetup.cs
public static string SQLSERVERNAME = "";
public static string HMSTENANTDB;
public static string SQLLOGIN;
public static string SQLPASSWORD;
public override void Commit(IDictionary savedState)
{
base.Commit(savedState);
try
{
SQLSERVERNAME = Context.Parameters["SQLSERVERNAME"];
HMSTENANTDB = Context.Parameters["HMSTENANTDB"];
SQLLOGIN = Context.Parameters["SQLLOGIN"];
SQLPASSWORD = Context.Parameters["SQLPASSWORD"];
}
catch (Exception e)
{
MessageBox.Show("Failed to update the application configuration file : " + e.Message);
base.Rollback(savedState);
}
}
from1.cs
InstallerSetup InsSetup = new InstallerSetup();
string Vsqlserver = InsSetup.Installers.Count.ToString();
string Vtenant = "";
if (InsSetup.Context != null)
{
Vtenant = InsSetup.Context.Parameters["HMSTENANTDB"];
}
else
{
Vtenant = "context is null";
}
As far as I can tell, the issue is that the property values are not being passed into the custom action. That would be the most obvious explanation. A commentfrom the poster says:
"passed those parameters to the custom action...................................... SQLSERVERNAME = Context.Parameters["SQLSERVERNAME"];
etc...
//................there is only these 4 lines in my custom actions"
which is essentially repeating the code that was previously posted.
This is NOT passing the values into the custom action. This is retrieving values which must already have been passed into the custom action.
Assuming that the custom action has been correctly added to (typically) the install nod of the custom action, and also assuming that the property names are in a TextBoxes dialog in the install, the values must be passed in to the custom action via the CustomActionData settings. To use one example, the CustomActionData setting must be something like:
/SQLSERVERNAME=[SQLSERVERNAME]
or /SQLSERVERNAME=[EDITA1] if EDIOTA1 is being used because that's the default property name.
However there is no reference to the TextBoxes (or any other) install dialog in the original question, so it's not really clear where the value of (say) SQLSERVERNAME is supposed to come from. It may be passed in on the msiexec command line, for example.

MongoDB Serialization C# - Adding Additional Encrypted Field Properties

I am trying to write a MongoDb serializer in c# that will allow me to decorate properties via a [Encrypt()] attribute and then at runtime it would allow me to generate an additional property called PropertyName_Encrypted which would contain the encrypted value.
On deserialization, the encrypted property value would be set in the parent property so that the default GET for the property always returns the encrypted value. Users will then call an optional Decrypt() method on the object to get decrypted values.
In doing so, I'm running into some interesting challenges:
How do I add Additional properties to the document when I am serializing current Element? How do I get the current element's name?
Is there a way I can read a specific property from the document/object? For e.g. say I want to pass a symmetric encryption key and read that to encrypt the data while serializing the current element? Is there any way I can do that?
Here are things I have done so far:
I've built an Encrypt Attribute as follows:
[AttributeUsage(AttributeTargets.Property)]
public class EncryptAttribute : Attribute
{
private readonly EncryptedFieldType _fieldType;
private readonly bool _tokenizeDisplay;
private readonly string _encryptedFieldName;
/// <summary>
///
/// </summary>
/// <param name="fieldType">The field type to encrypt. Useful if display needs to show some formatting. If no formatting is necessary, simply set to "Other".</param>
/// <param name="tokenizeDisplay">If set to true, will persist the tokenized value in the original field for display purposes.</param>
/// <param name="encryptedFieldName">Optional. If set, will save the encrypted value in the field name specified. By default all encrypted field values are stored in the corresponding _Encrypted field name. So EmailAddress field if encrypted, would have value under EmailAddress_Encrypted.</param>
public EncryptAttribute(EncryptedFieldType fieldType, bool tokenizeDisplay, string encryptedFieldName = "")
{
_fieldType = fieldType;
_tokenizeDisplay = tokenizeDisplay;
_encryptedFieldName = encryptedFieldName;
}
}
I read this Attribute on Startup and add an Encryption Serializer to the properties that are decorated using this attribute. The code that does that is like so:
var assemblies = AppDomain.CurrentDomain.GetAssemblies()
.Where(x => x.FullName.StartsWith("MongoCustomSerializer"))
.ToList();
var mapper = new Mapper();
foreach (var assembly in assemblies)
{
mapper.Map(assembly);
}
The mapper simply checks which properties in the document have the Encrypt attribute to add the serializer:
public sealed class Mapper
{
public void Map(Assembly assembly)
{
var encryptableTypes = assembly.GetTypes().Where(p =>
typeof(IEncryptable).IsAssignableFrom(p) && p.IsClass && !p.IsInterface && !p.IsValueType &&
!p.IsAbstract).ToList();
if (encryptableTypes.Any())
{
foreach (var encryptableType in encryptableTypes)
{
Map(encryptableType);
}
}
}
private void Map(Type documentType)
{
var properties =
documentType.GetProperties(BindingFlags.Public | BindingFlags.Instance);
if (properties.Length <= 0)
{
return;
}
foreach (var property in properties)
{
RegisterEncrpytionSerializer(property, typeof(EncryptAttribute), documentType);
}
}
private void RegisterEncrpytionSerializer(PropertyInfo property, Type encryptAttributeType, Type documentType)
{
var encryptAttributes = property.GetCustomAttributes(encryptAttributeType, false).ToList();
if (!encryptAttributes.Any()) return;
var memberMap = BsonClassMap.LookupClassMap(documentType).GetMemberMap(property.Name);
memberMap?.SetSerializer(new EncryptionSerializer());
}
}
In my unit tests, I'm getting an error stating that the Bson Class Map is already frozen. Even if I were to figure out a way to bypass that, how would this EncryptionSerializer class work to where I could write an additional property?
Would love to see if someone can assist!
UPDATE 1 - I was able to get the FREEZE error taken care of. It would appear that the LookupClassMap freezes the Member and Class Map info.
This change from the link allows me to take care of that issue:
private void RegisterEncrpytionSerializer(PropertyInfo property, Type encryptAttributeType, Type documentType)
{
var encryptAttributes = property.GetCustomAttributes(encryptAttributeType, false).ToList();
if (!encryptAttributes.Any()) return;
var classMapDefinition = typeof(BsonClassMap<>);
var classMapType = classMapDefinition.MakeGenericType(documentType);
var classMap = (BsonClassMap)Activator.CreateInstance(classMapType);
classMap.AutoMap();
var memberMap = classMap.GetMemberMap(property.Name);
memberMap?.SetSerializer(new KeyVaultEncryptionSerializer(memberMap.ElementName));
}
Are you using a service for saving/retrieving your items that actually call the DB?
I believe you should move the responsibility for writing/reading encrypted values to the calling service (i.e a repository implementation) instead of the BsonSerializer.
It would make sense to me that encryption/decryption is part of the persistence layer and something not handled in the application when needed.
Your implementation targets only the specified property you want to serialize. It doesn't make sense that it creates another property.
A second thought is that your suggested approach with properties that change value based on Decrypt() probably isn't a good idea since it makes your code unpredictable and hard to read. Make your properties dead simple.
What extra security in your code does it really give you if you can decrypt properties by just calling a method anyway?
If you still need to have a Decrypt() would suggest that you create methods for decrypting that return the decrypted value like GetUnencryptedCode() etc, it could just as well be an extension method but still not a readable property.
You should also be looking into using SecureString depending on your use case.

Roslyn Check If Field Declaration has been assigned to

I'm writing an app which converts keys to use resources from a RESX File. This code was working with local variables before:
public static void AnalyzeConstDeclaration(SyntaxNodeAnalysisContext context)
{
var fieldDeclaration = (FieldDeclarationSyntax)context.Node;
if (false == IsValidFieldDeclaration(context, fieldDeclaration))
{
return;
}
var firstVariable = fieldDeclaration.Declaration.Variables.FirstOrDefault();
var dataFlowAnalysis = context.SemanticModel.AnalyzeDataFlow(firstVariable);
var variableSymbol = context.SemanticModel.GetDeclaredSymbol(firstVariable);
if (dataFlowAnalysis.WrittenOutside.Contains(variableSymbol))
{
return;
}
var firstSymbol = context.SemanticModel.GetDeclaredSymbol(firstVariable);
context.ReportDiagnostic(Diagnostic.Create(Rule, context.Node.GetLocation(), firstSymbol.Name));
}
However when I try to get the dataFlowAnalysis I receive an error:
Additional information: statementOrExpression is not a StatementSyntax or an ExpressionSyntax.
How can Ideally just need to see if anyone has written to this variable outside of the declaration.
DataFlow works by analyzing order of execution within a single method.
It doesn't make sense for class-level fields.
Instead, you should use a simple syntax visitor (or SymbolFinder) to search the entire class for assignments to the field.
You'll probably also want to check whether it's ever passed as a ref parameter.

Categories

Resources