I'm having problems with [ObjectValidator]. So, i have:
public class UserBO
{
public int ID
{
get;
set;
}
[NotNullValidator(MessageTemplate = "Can't be null!")]
[RegexValidator(#"[a-z]|[A-Z]|[0-9]*", MessageTemplate = "Must be valid!", Ruleset = "validate_username")]
[StringLengthValidator(5, RangeBoundaryType.Inclusive, 25, RangeBoundaryType.Inclusive, Ruleset = "validate_username")]
public string username
{
get;
set;
}
and another class:
public class PersonBO
{
public int ID
{
get;
set;
}
[NotNullValidator(MessageTemplate="Can't be null!")]
[ObjectValidator(MessageTemplate = "Must be valid!", Ruleset="validate_obj_user")]
public UserBO User
{
get;
set;
}
...
Now can you tell me why the following test passes?
[TestMethod()]
public void PersonBOConstructorTest()
{
PersonBO target = new PersonBO()
{
User = new UserBO
{
ID = 4,
username = "asd"
}
};
ValidationResults vr = Validation.Validate<PersonBO>(target, "validate_obj_user");
Assert.IsTrue(vr.IsValid);
}
This should not be valid, because: User attribute (of UserBO type) contains username "asd" (3 characters), and i defined for it a StringLengthValidator (between 5 and 25 characters).. so why the test passes? that object is not valid
I can't understand.
Thanks.
You have to specify a ruleset in your ObjectValidator if you want rules from a set other than the default set applied.
[ObjectValidator("validate_username", MessageTemplate = "Must be valid!", Ruleset = "validate_obj_user")]
The above should work in this specific case. Alternatively, you could remove the ruleset parameter from the string length validator to leave it in the default set.
Related
I have below the object and its internal objects and I have added custom attributes to the property names which are required for the report.
public class Space
{
public SpaceIdentity SpaceIdentity { get; set; } = new();
public SpaceGeometry SpaceGeometry { get; set; } = new();
public AirBalance AirBalance { get; set; } = new();
public EngineeringChecks EngineeringChecks { get; set; } = new();
}
public class SpaceIdentity
{
public int ElementId { get; set; } // Not required
[DisplayNameWithUnits(DisplayName = "Space Number", IsIncludedInReport2 = true)]
public string Number { get; set; }
[DisplayNameWithUnits(DisplayName = "Space Name", IsIncludedInReport2 = true, IsIncludedInReport1 = true)]
public string Name { get; set; }
[DisplayNameWithUnits(DisplayName = "Room Number", IsIncludedInReport1 = true)]
public string RoomNumber { get; set; }
[DisplayNameWithUnits(DisplayName = "Room Name", IsIncludedInReport1 = true)]
public string RoomName { get; set; }
}
public class SpaceGeometry
{
public Vertex LocationPoint { get; set; } // this is not required
[DisplayNameWithUnits(DisplayName = "Space Area", Units = "(ft²)", IsIncludedInReport1 = true)]
public double FloorArea { get; set; }
}
.....
Here I am building an excel report, which I want to use property display name's as header column names of that report. Here are some of the properties attribute information used in multiple reports. What I did was I added a bool condition attribute like (isIncludedInReport1) and loop through the properties of space and loop through the properties of inner object(SpaceGeometry) to get a particular property name and its attribute values based on this boolean condition.
What I am looking for here is without adding these bool attributes, is there any way to access the property names based on condition. I thought about adding interfaces, but that is not possible here because I have multiple inner classes having properties that I need to include in a single report.
Could anyone please let me know is there any other way to achieve this?
Update:
var columnResult = new OrderedDictionary();
GetReportHeaderColumnName(typeof(Space), columnResult);
public static void GetReportHeaderColumnName(Type type, OrderedDictionary headerNameByUnit)
{
var properties = type.GetProperties();
foreach (var propertyInfo in properties)
{
if (propertyInfo.PropertyType.IsClass && !propertyInfo.PropertyType.FullName.StartsWith("System."))
{
if (propertyInfo.PropertyType == typeof(Overridable<double>))
{
AddReportHeaderName(headerNameByUnit, propertyInfo);
}
else
{
GetReportHeaderColumnName(propertyInfo.PropertyType, headerNameByUnit);
}
}
else
{
AddReportHeaderName(headerNameByUnit, propertyInfo);
}
}
}
protected static void AddReportHeaderName(OrderedDictionary columnResult, PropertyInfo propertyInfo)
{
if (propertyInfo.GetCustomAttributes(typeof(DisplayNameWithUnitsAttribute), true).Any())
{
var displayNameWithUnitsAttribute = (DisplayNameWithUnitsAttribute)Attribute.GetCustomAttribute(propertyInfo, typeof(DisplayNameWithUnitsAttribute));
if (displayNameWithUnitsAttribute.IsIncludedInReport2)
{
columnResult.Add(displayNameWithUnitsAttribute.DisplayName, displayNameWithUnitsAttribute.Units);
}
}
}
Another way that doesn't use reflection is to just use a list of ReportProperty
public record ReportProperty<T>(
Func<T, string> ValueFunc,
string DisplayName,
string? Unit = null
);
List<ReportProperty<Space>> report1 = new(){
new( s => s.SpaceIdentity.Number, "Space Number"),
new( s => s.SpaceIdentity.RoomNumber, "Room Number"),
new( s => s.SpaceIdentity.RoomName, "Room Name"),
new( s => s.SpaceGeometry.FloorArea.ToString(), "Floor Area", "Ft2"),
};
What do you want to achieve by making it another way? I assume you want to make your code scalable so adding more reports is easier.
Do you still want to use attributes on the classes themselves? If so, you could make a new attribute, and tag your properties like so:
public class SpaceIdentity
{
public int ElementId { get; set; } // Not required
[DisplayNameWithUnits(DisplayName = "Space Number")]
[IncludeInReport(2)]
public string Number { get; set; }
[DisplayNameWithUnits(DisplayName = "Space Name")]
[IncludeInReport(1)]
[IncludeInReport(2)]
public string Name { get; set; }
[DisplayNameWithUnits(DisplayName = "Room Number")]
[IncludeInReport(1)]
public string RoomNumber { get; set; }
[DisplayNameWithUnits(DisplayName = "Room Name")]
[IncludeInReport(1)]
public string RoomName { get; set; }
}
Or do you want to separate the report from the classes, so your report is defined in another file? If so, maybe a 2-dimensional list of property names would do the trick:
List<List<string>> Report1 = new()
{
new(){nameof(Space.SpaceIdentity), nameof(SpaceIdentity.Name)},
new(){nameof(Space.SpaceIdentity), nameof(SpaceIdentity.RoomNumber)},
new(){nameof(Space.SpaceIdentity), nameof(SpaceIdentity.RoomName)},
new(){nameof(Space.SpaceGeometry), nameof(SpaceGeometry.FloorArea)}
};
I am cassandra for custom logging my .netcore project, i am using CassandraCSharpDriver.
Problem:
I have created UDT for params in log, and added list of paramUDT in Log table as frozen.
But i am getting error: Non-frozen UDTs are not allowed inside collections. I don't know why ia m getting this error because i am using Frozen attribute on list i am using in Log Model.
logSession.Execute($"CREATE TYPE IF NOT EXISTS {options.Keyspaces.Log}.{nameof(LogParamsCUDT)} (Key text, ValueString text);");
Here is model:
public class Log
{
public int LoggingLevel { get; set; }
public Guid UserId { get; set; }
public string TimeZone { get; set; }
public string Text { get; set; }
[Frozen]
public IEnumerable<LogParamsCUDT> LogParams { get; set; }
}
Question where i am doing wrong, is my UDT script not correct or need to change in model.
Thanks in advance
I've tried using that model and Table.CreateIfNotExists ran successfully.
Here is the the code:
public class Program
{
public static void Main()
{
var cluster = Cluster.Builder().AddContactPoint("127.0.0.1").Build();
var session = cluster.Connect();
session.CreateKeyspaceIfNotExists("testks");
session.ChangeKeyspace("testks");
session.Execute($"CREATE TYPE IF NOT EXISTS testks.{nameof(LogParamsCUDT)} (Key text, ValueString text);");
session.UserDefinedTypes.Define(UdtMap.For<LogParamsCUDT>($"{nameof(LogParamsCUDT)}", "testks"));
var table = new Table<Log>(session);
table.CreateIfNotExists();
table.Insert(new Log
{
LoggingLevel = 1,
UserId = Guid.NewGuid(),
TimeZone = "123",
Text = "123",
LogParams = new List<LogParamsCUDT>
{
new LogParamsCUDT
{
Key = "123",
ValueString = "321"
}
}
}).Execute();
var result = table.First(l => l.Text == "123").Execute();
Console.WriteLine(JsonConvert.SerializeObject(result));
Console.ReadLine();
table.Where(l => l.Text == "123").Delete().Execute();
}
}
public class Log
{
public int LoggingLevel { get; set; }
public Guid UserId { get; set; }
public string TimeZone { get; set; }
[Cassandra.Mapping.Attributes.PartitionKey]
public string Text { get; set; }
[Frozen]
public IEnumerable<LogParamsCUDT> LogParams { get; set; }
}
public class LogParamsCUDT
{
public string Key { get; set; }
public string ValueString { get; set; }
}
Note that I had to add the PartitionKey attribute or else it wouldn't run.
Here is the CQL statement that it generated:
CREATE TABLE Log (
LoggingLevel int,
UserId uuid,
TimeZone text,
Text text,
LogParams frozen<list<"testks"."logparamscudt">>,
PRIMARY KEY (Text)
)
If I remove the Frozen attribute, then this error occurs: Cassandra.InvalidQueryException: 'Non-frozen collections are not allowed inside collections: list<testks.logparamscudt>'.
If your intention is to have a column like this LogParams frozen<list<"testks"."logparamscudt">> then the Frozen attribute will work. If instead you want only the UDT to be frozen, i.e., LogParams list<frozen<"testks"."logparamscudt">>, then AFAIK the Frozen attribute won't work and you can't rely on the driver to generate the CREATE statement for you.
All my testing was done against cassandra 3.0.18 using the latest C# driver (3.10.1).
I have an enumeration
public enum GTMType
{
[Display(Name = "CHANNEL_CHANNEL")]
ChannelChannel,
[Display(Name = "CHANNEL_WHOLESALE")]
ChannelWholesale,
[Display(Name = "ENTERPRISE_DIRECT")]
EnterpriseDirect,
[Display(Name = "ENTERPRISE_AGENT")]
EnterpriseAgent,
[Display(Name = "ENTERPRISE_SYSTEM_INTEGRATOR")]
EnterpriseSystemIntegrator
}
when I make an API call to another system to get data, The system returns the value which is a display attribute value.
public Account GetDataForAccountByID(string id)
{
AccountModel accountModel = GetDataFromAnotherSystem(id);
//after the call is successfull accountModel looks like
//{Email: "abc#xyz.com",GTMType:"CHANNEL_CHANNEL"}
var account = new Account
{
EmailAddress: = accountModel.Email,
GTMType = accountModel.GTMType
};
}
public class AccountModel
{
public string Email { get; set; }
public string GTMType { get; set; }
}
public class Account
{
public string EmailAddress { get; set; }
public GTMType GTMType { get; set;
}
How can I convert the string value which is of display attribute value can be converted into an enum.
You can use Enum.GetValues to iterate over possible values of the enum and then GetField representing the given value and get its attributes.
GTMType ParseGTMTypeFromAnotherSystem(string gtmType)
{
var type = typeof(GTMType);
foreach (var value in Enum.GetValues(type))
{
var fieldInfo = type.GetField(Enum.GetName(type, value));
DisplayAttribute[] attributes = fieldInfo.GetCustomAttributes(typeof(DisplayAttribute), false) as DisplayAttribute[];
if (attributes.Length != 1)
{
throw new Exception("Enum definition is wrong.");
}
var displayName = attributes[0].Name;
if (gtmType == displayName)
{
return value as GTMType;
}
}
throw new Exception("Unable to parse.");
}
You may want to handle the exception cases differently, e.g. iterate over all DisplayAttributes in case there's more than one and ignore fields that have none, or in case of a failed parse return a default value - depends on your use case.
I have an action method which outputs a model which has multiple sub models. In one of the sub model I have some additional properties which are not required in my view.
Sub model- ProjectModel-
[Required(ErrorMessage = "*")]
public int Id { get; set; }
[Required(ErrorMessage = "*")]
public int SectorDivisionId { get; set; }
[Required(ErrorMessage = "*")]
[StringLength(250, ErrorMessage = "Project name should not be more than 250 characters.")]
public string Program { get; set; }
[Required(ErrorMessage = "*")]
[StringLength(25, ErrorMessage = "Project number should not be more than 25 characters.")]
public string ProjectNumber { get; set; }
public string WorkPackage { get; set; }
public string WorkPackageType { get; set; }
[Required(ErrorMessage = "*")]
public DateTime StartDate { get; set; }
[Required(ErrorMessage = "*")]
public DateTime EndDate { get; set; }
public int ProjectDirectorId { get; set; }
So while initializing the sub model to my main model I am only using those properties which I need as shown below.
model.ProjectInfo = new ProjectModel()
{
Id = projectId,
ProjectNumber = prj.p.ProjectNumber,
Director = prj.Director,
Program = prj.p.Program,
StartDate = prj.p.StartDate,
EndDate = prj.p.EndDate,
ProjectReviewPeriodList = projectReviewPeriodList.AsEnumerable().
Select(o => new ProjectReviewPeriodModel
{
Id = o.Id,
ProjectReviewTypeId = o.ProjectReviewTypeId,
ProjectId = o.ProjectId,
ReviewPeriod = o.ReviewPeriod,
ReviewPeriodDate = o.ReviewPeriodDate
}).ToList()
};
Now, while posting the form I have an action filter at global level which validates the Model. The validation (ModelState.IsValid) fails for some of the fields from the sub model which I haven't initialized as per my needs.
I thought of two options-
Using ModelState.Remove(<PropertyName>) to skip validation. This is not possible as I am using a global level action filter.
Create a new view model
Is there any other way of doing this, preferably in the action method level?
Please let me know if any doubts or I can explain it more clearly.
Thanks.
The clean way would be to use different ViewModels for different usecases.
That being said, you can implement the validation logic with IValidatableObject instead of using Data Annotations attributes.
Introduce a flag into the ViewModel that indicates the usecase, e.g. IsEditUsecase. Set this flag somewhere where you know the usecase, e.g. in the controller.
Then only perform the validations that are needed for this usecase.
public class ProjectModel : IValidatableObject {
public bool IsEditUsecase { get; set; }
[Required(ErrorMessage = "*")] // required for every usecase
public int Id { get; set; }
// no [Required] and [StringLength] attributes
// because only required in some of the usecases
public string Program { get; set; }
public IEnumerable<ValidationResult> Validate(ValidationContext validationContext) {
// validation that occurs only in Edit usecase
if (IsEditUsecase) {
// Validate "Program" property
if (string.IsNullOrWhiteSpace(Program)) {
yield return new ValidationResult(
"Program is required",
new[] { "Program" }
);
}
else if (Program.Length > 250) {
yield return new ValidationResult(
"Project name should not be more than 250 characters.",
new[] { "Program" }
);
}
// validate other properties for Edit usecase ...
}
// validate other usecases ...
}
}
As a dirty hack, I have added hidden fields in my razor page for all those properties which caused ModelState validation error. Basically I added some default values for the hidden fields and it works fine now.
Not recommended though but it was a quick fix.
I am using .NET 4 MVC 2 in my project. I basically have two classes, which I use for my validation. Class A is my (main) model, class B is an composite attribute which class A may have. The code looks like the following:
[Bind(Exclude = "A_ID")]
public class A_Validation
{
[Required(ErrorMessage = "something is missing")]
public string title { get; set; }
// some more attributes ...
public B b { get; set; }
}
All my validation based on class A is working very well. But now I want to validate the composite attribute B, which looks like the following.
[Bind(Exclude = "B_ID")]
public class B_Validation
{
[Required(ErrorMessage = "missing")]
[Range(1, 210, ErrorMessage = "range between 1 and 210")]
public int first { get; set; }
[Required(ErrorMessage = "missing")]
[Range(1, 210, ErrorMessage = "range between 1 and 210")]
public int second { get; set; }
[Required(ErrorMessage = "missing")]
[Range(1, 210, ErrorMessage = "range between 1 and 210")]
public int third { get; set; }
}
I am able to check the ranges of B's three attributes first,second and third. What I additionally want is to check if the sum of all three attributes first,second and third is below a certain threshold.
Any ideas how to proceed?
I think ViewModels might help, but i have no experience in using them.
Have you tried writing a custom validation attribute:
public class SumBelowAttribute : ValidationAttribute
{
private readonly int _max;
public SumBelowAttribute(int max)
{
_max = max;
}
public override bool IsValid(object value)
{
var b = value as B_Validation;
if (b != null)
{
return b.first + b.second + b.third < _max;
}
return base.IsValid(value);
}
}
and then decorate the B property with this attribute:
[SumBelow(123)]
public B b { get; set; }