Best pattern to validate DataContract properties conditionally - c#

I have data class using DataContract and implementing field validation using System.ComponentModel.DataAnnotations.
In my class, the type attribute renders some fields relevant while others are not applicable. That is, some fields, depending on the type attribute, will become invalid while others are valid.
I am trying to find a good pattern to validate such conditions in an elegant fashion. I am open to accepting that running into this situation might mean I need to break my class in a few chunks to accommodate such polymorphism. Although not sure how.
Just in case it is relevant, those data classes will be serialized and stored on ServiceFabric reliable collections. This validation if more from an API perspective.
As of today, I am performing the validation in this way (which I do not find satisfactory) in the endpoint controller.
if (voucher.ServiceType == ServiceType.VaccineCompany)
{
if (voucher.Asignee == null)
{
throw new ArgumentNullException("Asignee is required for company vaccine voucher");
}
if (!voucher.BookingTime.HasValue)
{
throw new ArgumentNullException("Booking time is required for company vaccine voucher");
}
if (!voucher.FixedPrice.HasValue)
{
throw new ArgumentNullException("Fixed price is required for company vaccine voucher");
}
if (voucher.Discount.HasValue)
{
throw new ArgumentNullException("Discount is not a valid argument for company vaccine type voucher");
}
}
if (voucher.ServiceType == ServiceType.Vaccine)
{
if (voucher.Asignee != null)
{
throw new ArgumentException("Invalid argument asignee");
}
if (voucher.BookingTime.HasValue)
{
throw new ArgumentNullException("Invalid argument booking time");
}
if (voucher.FixedPrice.HasValue && voucher.Discount.HasValue)
{
throw new ArgumentException("Fixed price and discount cannot be set simultaneously");
}
}
This is how the model looks like:
using System;
using System.Collections.Generic;
using System.ComponentModel.DataAnnotations;
using System.Runtime.Serialization;
namespace Remoting.VoucherService
{
[DataContract]
public sealed class Voucher
{
[Required(ErrorMessage = "Code is required")]
[DataMember(Name = "code")]
public string Code { get; set; }
[Required(ErrorMessage = "Description is required")]
[DataMember(Name = "description")]
public string Description { get; set; }
[Required(ErrorMessage = "ServiceType is required")]
[DataMember(Name = "serviceType")]
public ServiceType ServiceType { get; set; }
[DataMember(Name = "discount")]
public double? Discount { get; set; }
[DataMember(Name = "fixedPrice")]
public double? FixedPrice { get; set; }
[DataMember(Name = "isValid")]
public bool Valid {
get { return TimesUsed < MaxUsage; }
set { }
}
[DataMember(Name = "maxUsage")]
public uint MaxUsage { get; set; } = 1;
[DataMember(Name = "asignee")]
public string Asignee;
[DataMember(Name = "bookingTime")]
public DateTime? BookingTime;
[DataMember(Name = "timesUsed")]
public uint TimesUsed { get; set; } = 0;
public void IncreaseUsage()
{
TimesUsed += 1;
}
private ExtensionDataObject data;
public ExtensionDataObject ExtensionData
{
get => data;
set => data = value;
}
}
}
I hope somebody can provide me with some insights!

Related

Blazor trigger custom validation message

I have the following class which is being used as an input model for an EditForm in a Blazor server side application.
public class KundeInput
{
[ValidateComplexType]
public List<AnsprechpartnerInput> Ansprechpartner { get; } = new List<AnsprechpartnerInput>();
public string? Kundennummer { get; }
[Required]
[MaxLength(60)]
public string Firma { get; set; } = String.Empty;
[MaxLength(60)]
public string? Name2 { get; set; }
[MaxLength(60)]
public string? Name3 { get; set; }
}
As you can see, my model contains a list of another model called AnsprechpartnerInput. Here is this model:
public class AnsprechpartnerInput
{
public string? Kundennummer { get; set; }
public int Nummer { get; } = -1;
[MaxLength(60)]
[Required]
public string Vorname { get; set; } = String.Empty;
[MaxLength(60)]
[Required]
public string Nachname { get; set; } = String.Empty;
[MaxLength(40)]
[Required]
public string? Bereich { get; set; }
/ * More properties */
}
The validation works fine. However, once I have multiple invalid AnsprechpartnerInput models in my list, the ValidationSummary becomes a mess. Because it displays e.g. 5 times field xyz is invalid.
I know I can set a custom message with the ErrorMessage property but I am not able to use other attributes from my model in this message.
What I want to achive is this:
[Required(ErrorMessage = $"Vorname of {Kundennummer} is required")]
public string Vorname { get; set; } = String.Empty;
I already tried to change the message with reflection but accoridng to Microsoft this way is not recommend or supported
https://github.com/dotnet/aspnetcore/issues/25611
Is there any way to get it to work? I thought of string replacement but I am not sure how I can figure out the right model for my ValidationMessage.
Also is there any way to validate the items of the list by one and get a boolean result? Let's say I want to achive this:
#foreach (var ansprechpartner in Input.Ansprechpartner)
{
if (Input.SelectedAnsprechpartner is null)
Input.SelectedAnsprechpartner = ansprechpartner;
<a #onclick="() => Input.SelectedAnsprechpartner = ansprechpartner"
class="#GetNavListClass(Input.SelectedAnsprechpartner == ansprechpartner)"
id="list-ansprechpartner-tab-#(ansprechpartner.Nummer)"
data-toggle="list"
href="#list-ansprechpartner-#(ansprechpartner.Nummer)"
role="tab"
aria-controls="#(ansprechpartner.Nummer)">
#((MarkupString)(ansprechpartner.Nummer < 0 ? "<span class=\"font-weight-bold\">NEU</span>" : $"({ansprechpartner.Nummer})")) #ansprechpartner.Vorname #ansprechpartner.Nachname
</a>
// When the model ansprechpartner is invalid, I want to display an icon
}
Thanks for any help!
PS: Blazor rocks!
You should use a custom validation attribute where you can explicitly add any error message you want
public class KundennummerValidationAttribute : ValidationAttribute
{
protected override ValidationResult IsValid(object value, ValidationContext validationContext)
{
var model = (AnsprechpartnerInput)validationContext.ObjectInstance;
if(string.IsNullOrEmpty((string)value))
{
return new ValidationResult($"Vorname of {model.Kundennummer} is required", new[] { "Kundennummer" });
}
return ValidationResult.Success;
}
}
then use
[KundennummerValidation]
public string Vorname { get; set; } = String.Empty;
result :
Validation summary:

Compare Multiple Variables to Same Value Efficiently

I'm looking for alternative methods in C# to compare multiple variables to the same value, and I would optimally like for them to share the same subsequent instructions based on their conditional results.
For example, I have the following code:
string DOBResultsError = VerifySingleRecordReturned(DOBResults, APIParameters, 1);
string NameResultsError = VerifySingleRecordReturned(NameResults, APIParameters, 2);
if (DOBResultsError != string.Empty)
{
PatientRecordUpdate(DOBResults, APIParameters.PatientID, DOBResultsError);
}
else if (NameResultsError != string.Empty)
{
PatientRecordUpdate(NameResults, APIParameters.PatientID, NameResultsError);
}
I'm having to do explicitly instruct PatientRecordUpdate to be performed for each variable being compared to String.Null.
What I would like to have happen is something like the following:
if (DOBResultsError != string.Empty || NameResultsError != string.Empty)
{
//whichever isn't an empty string use to perform PatientRecordUpdate()
}
Is such syntax possible in C#?
Employing the switch keyword won't make a difference because even though I can have multiple circumstances resulting in the same instructions being performed, if I need to use the one of the comparison variables I would still need to explicitly state the code using the variable for each possible case.
string DOBResultsError = VerifySingleRecordReturned(DOBResults, APIParameters, 1);
string NameResultsError = VerifySingleRecordReturned(NameResults, APIParameters, 2);
string SSNResultsError = VerifySingleRecordReturned(SSNResults, APIParameters, 3);
string EmptyString = String.Empty;
switch (EmptyString)
{
case DOBResultsError:
case SSNResultsError: //can't use SSNResultsError with PatientRecordUpdate() without stating PatientRecordUpdate() again
PatientRecordUpdate(DOBResults, APIParameters.PatientID, DOBResultsError);
case NameResultsError:
PatientRecordUpdate(NameResults, APIParameters.PatientID, NameResultsError);
}
Any help appreciated.
UPDATE: Requested additional info
This is what VerifySingleRecordReturnedFrom() does. It checks a few conditions that would cause errors in the program and writes an error message to be added on the record in an SQL DB.
public static string VerifySingleRecordReturnedFrom(List<PatientList3> ReturnedPatientList, AutoPatientLookup APIParameters, int SearchCriteria = 0)
{
string ErrorMessage = String.Empty;
if (ReturnedPatientList.Count == 0)
{
//Error Message for Dob
if (SearchCriteria == 1)
{
ErrorMessage = string.Format("No patients were returned from for DOB ....");
return ErrorMessage;
}
//Error Message for Name
else if (SearchCriteria == 2)
{
ErrorMessage = string.Format("No patients were returned from for patient name ....");
return ErrorMessage;
}
//Error Message for PracticePatientNumber
else if (SearchCriteria == 3)
{
ErrorMessage = string.Format("No patients were returned from for PracticePatientNumber...");
return ErrorMessage;
}
}
// more than one patient in common results list from AttemptToMatchPatientsByDemographics() or results using PatientNumber
else if (ReturnedPatientList.Count() > 1)
{
switch(SearchCriteria)
{
case 1:
case 2:
ErrorMessage = String.Format("{0} number of patients were returned...");
break;
//More than one patient returned from for any given PracticePatientNumber
case 3:
ErrorMessage = String.Format("{0} number of patients were returned....");
break;
}
return ErrorMessage;
}
//No error in number of results from
return ErrorMessage;
}
All of the results(DOB/Name/SSN) types are List objects of the following PatientList3 object (I've included sub classes):
public class PatientList3
{
public Patient PatientNameID { get; set; }
public string PatientNumber { get; set; }
public string ChartNumber { get; set; }
public Gender2 Gender { get; set; }
public string DOB { get; set; }
public string PhoneNumber { get; set; }
public string SSN { get; set; }
}
public class Patient
{
public int ID { get; set; }
public PtName Name { get; set; }
}
public class PtName
{
public string First { get; set; }
public string Middle { get; set; }
public string Last { get; set; }
public string Suffix { get; set; }
public string Full { get; set; }
public string Preferred { get; set; }
}
public class Gender2
{
public string LookupType { get; set; }
public string Code { get; set; }
public string Description { get; set; }
public int Order { get; set; }
public bool Active { get; set; }
public List<AlternateCodes> AlternateCodes { get; set; } //Not important, didn't include AlternativeCodes class
}
This is the class of APIParameters:
public class AutoPatientLookup
{
public string DOB { get; set; }
public string Gender { get; set; }
public string FirstName { get; set; }
public string LastName { get; set; }
public int? PatientNumber { get; set; }
public string SSN { get; set; }
public int PracticeID { get; set; }
public int PatientID { get; set; }
}
Consider leveraging a base result type for some generic logic like this:
// assuming both results inherit from or implement some base type...
List<ResultBase> results = new List<ResultBase>()
{
DOBResults,
NameResults
}; // order matters since it's used for the parameter index
// Note that a class would be a much better option than a tuple here.
// Select will verify each result but only upon request until FirstOrDefault is fulfilled
Tuple<ResultBase, string> firstResultError = results
.Select((result, index) => new Tuple<ResultBase, string>(
result,
VerifySingleRecordReturned(result, APIParameters, index + 1)))
.FirstOrDefault(result => !string.IsNullOrEmpty(result.Item2 /* Error message */));
// If there was at least one error, call the original patient update record
// with the associated information.
if (firstResultError != null)
{
PatientRecordUpdate(
firstResultError.Item1 /* Failed result */,
APIParameters.PatientID,
firstResultError.Item2 /* Error message for that result */);
}
You'll want to use a new class instead of a tuple for maintainability reasons, but besides that this should get you started in a good direction.

Throwing a exception on Constructor

I'm creating a new webapi pattern and i decided use a new pattern to make my consistency.
I'm trying to set my error messages on the constructor of my class object Pessoa
public class PessoaModel
{
public int PessoaId { get; set; }
public string PessoaNome { get; set; }
public string PessoaNomeFantasia { get; set; }
public string PessoaCnpjCpf { get; set; }
public string PessoaEmail { get; set; }
PessoaModel()
{
if (PessoaNome == null)
throw new Exception("Preencha Nome");
if (PessoaEmail == null)
throw new Exception("Preencha Email");
if (PessoaCnpjCpf == null)
throw new Exception("Preencha Cpf ou Cnpj");
}
}
Then the Exception happens but the Controllers continues running
[HttpPost]
[Route("Pessoa")]
public IHttpActionResult Post(PessoaModel pessoa)
{
if (_pessoa.Insert(pessoa))
return Ok();
return BadRequest("Pessoa não inserida");
}
Someone know how this work or if have a better way to do this?
What you are doing doesn't make sense. You are forcing properties to have a value, where you have done nothing to give them a value. At that time, they can only be set from the constructor.
Since you are using MVC/Web API, I would consider to use data annotations, to enforce the model to have the correct values.
public class PessoaModel
{
[Required(ErrorMessage = "ID is required.")]
public int PessoaId { get; set; }
}
In your action:
if (!this.ModelState.IsValid)
{
return RedirectToAction("Error"); // give an error, do something else
}

Generics where <t> Class library and MVC app

I'm trying to learn how to use the 'where' in Generic, I seem to have it working but I not sure if how I have done it is the correct way.
Currently I have in a class library:
public class NewMembersViewModel
{
public NewMembersViewModel()
{
Id = Guid.NewGuid().ToString();
}
[Display(Name = "Username")]
[RemoteUsernameValidation("IsUserNameTaken", "Register", ErrorMessage = "Username already taken")]
public string MemberUsername { get; set; }
[Display(Name = "Password")]
public string MemberPassword { get; set; }
[Display(Name = "Email")]
[RemoteEmailValidation("IsEmailTaken", "Register", ErrorMessage = "Email already in use")]
public string MemberEmail { get; set; }
[Display(Name = "Mobile Number")]
[RemoteMobileValidation("IsMobileTaken", "Register", ErrorMessage = "Mobile Number already in use")]
public string MemberMobile { get; set; }
[Display(Name = "Forename")]
public string MemberForename { get; set; }
[Display(Name = "Surname")]
public string MemberSurname { get; set; }
public string Id { get; set; }
public int VerificationCode { get; set; }
}
And
public static class SqlDatabaseMethods<T> where T : NewMembersViewModel
{
public static bool LoginUser(T member)
{
var p = member.MemberEmail;
return true;
}
public static bool AddNewMember(T member)
{
var name = member.MemberForename;
return true;
}
}
Then in my MVC app I have:
bool name = SqlDatabaseMethods<NewMembersViewModel>.AddNewMember(model);
if (name)
{
var test = "works";
}
This does work, can anyone tell me whether I doing it correctly, as all examples I have seen appear to be console apps
Generics make sense when you need have some functionality which is common across multiple classes. In this case where clause defines what the constraints of your generic type are. In such case generic methods help to re-use functionality.
In your case it doesn't seem that SqlDatabaseMethods<T> could be re-used more than once, as model NewMembersViewModel seems to be very specific and hardly re-usable elsewhere.
In your code you are using generics correctly, but in my opinion you don't need generics here at all. You could just as easily use the following code without the overhead of generics:
public static class SqlDatabaseMethods
{
public static bool LoginUser(NewMembersViewModel member)
{
var p = member.MemberEmail;
return true;
}
public static bool AddNewMember(NewMembersViewModel member)
{
var name = member.MemberForename;
return true;
}
}
And then call the method like this:
bool name = SqlDatabaseMethods.AddNewMember(model);
if (name)
{
var test = "works";
}

Web Service Exception: "The formatter threw an exception while trying to deserialise the message"

Got a question. I get this error and I know it is due to the fact that int32 has a number limit of 2147483647. But I don't know why I am getting this error when the value in question (a telephone number of 11 digits) is defined as a string in our SQL database, a string in our web service and a string in our web application.
I assume it is something to do with the way the service serialises and deserialises data over a connection, but I was wanting to know if there is a way to force Number to use only the string instead of parsing it when deserialisation happens. Or even get it to parse as int64.
Here is the error exception. I removed the namespace and service name. It is the property Number that is causing the problem.
There was an error deserializing the object of type .".ClientPhone[]. The value '07721545554' cannot be parsed as the type 'Int32'."
And here is the code for the service and the service interface.
[DataContract]
public class ClientPhone
{
[DataMember]
public int? ClientNumberID { get; set; }
[DataMember]
public int? RefID { get; set; }
[DataMember]
public string Number { get; set; }
[DataMember]
public string NumberType { get; set; }
[DataMember]
public bool? PrimaryNumber { get; set; }
}
public partial class ClientNumberEntity
{
public int ClientNumbersID { get; set; }
public Nullable<int> RefID { get; set; }
public string ClientNumberType { get; set; }
public string ClientNumber { get; set; }
public Nullable<bool> PrimaryNumber { get; set; }
public virtual ClientDataEntity ClientData { get; set; }
}
public List<ClientPhone> GetClientsPhoneByReference(int _reference)
{
OurDatabaseEntities context = new OurDatabaseEntities();
var phoneEntity = (from c in context.ClientNumberEntities
where c.RefID == _reference
select c).ToList();
if (phoneEntity != null)
{
return TranslateClientPhoneEntityToPhoneNumberList(phoneEntity);
}
else
throw new Exception("Unable to get phone data");
}
private List<ClientPhone> TranslateClientPhoneEntityToPhoneNumberList(List<ClientNumberEntity> numberEntities)
{
List<ClientPhone> phoneList = new List<ClientPhone>();
foreach (ClientNumberEntity numberEntity in numberEntities)
{
ClientPhone phoneListMember = new ClientPhone();
phoneListMember.ClientNumberID = numberEntity.ClientNumbersID;
phoneListMember.RefID = numberEntity.RefID;
phoneListMember.Number = numberEntity.ClientNumber;
phoneListMember.NumberType = numberEntity.ClientNumberType;
phoneListMember.PrimaryNumber = numberEntity.PrimaryNumber;
phoneList.Add(phoneListMember);
}
return phoneList;
}
Any advice on a solution would be greatly appreciated! Thanks :)
Got a solution, albeit it's more stupidity on my end.
I didn't realise that my .EDMX entity diagram hadn't been updated with the new values from the database (I had to manually delete the entity and re-add it to force changes).
After re-compiling and updating the service reference, it worked.

Categories

Resources