A good practice for setting computed properties - c#

I am wondering what is a "good practice" for setting properties that are not mapped in the database. I am working with EF Core, but this is really more of a question of design. Imagine I have a following class:
class User
{
public string FirstName { get; set; }
public string LastName { get; set; }
[NotMapped]
public string Name { get; set; }
...
}
Where Name would be computed after fetching the the FirstName and LastName from the database. I am trying to prevent calling the getter for Name without setting the property first.
I know I could do something like
class User
{
...
private string _name = null;
[NotMapped]
public string Name {
get
{
if (_name == null)
_name = $"{FirstName} {LastName}";
return _name;
}
}
...
}
But this gets really messy when multiple properties need to be set this way and when they are obtained in a more complicated way than this one-liner.
For now I wrote a function SetAllProperties() which sets all NotMapped properties, however I don't consider it a good solution since there is a risk of forgetting
To update it when new NotMapped properties are added
To actually call it before accessing the properties
Is an interface a way to go? How should it look like? Or is there some "standard" way of dealing with this? I wasn't able to find anything useful on this topic.

Related

Hide model classmember in controller in C# Web API?

I am trying to learn and understand C# Web API and MVC.
I understand the simple tutorials where one has a simple Product or Person class as a Model and then makes a CRUD Controller to make use of the model.
But I need it to be a bit more complex and can't figure it out.
I have following Model:
public class PersonModel
{
public int Id { get; set; }
public string Name { get; set; }
public string Title { get; set; }
public DateTime LastUpdated { get; set; }
}
Same as the table in my database. The LastUpdated column has a default constraint: (GETUTCDATE())
I am not interrested in exposing LastUpdated in my POST methods in PersonsController:
public void PostPerson(PersonModel person)
{
// Upload person to database
}
Because then one could insert an invalid datetime in LastUpdated - or I have to manuel set LastUpdated in my business logic, but why not just let my SQL server do it?
Anyway to hide LastUpdated in PostPerson?
As a sidenote I would like to be able to show LastUpdated in my GetPerson method.
How is that possible?
When you implement a property in a class, you can specify different access modifiers for the get vs. set accessors.
This is true whether you are implementing the property yourself, or using an automatic property.
Different combinations of access modifiers include:
get/set both public – client can read/write property value
get/set both private – client has no access to the property
get public, set private – property is read-only
get private, set public – property is write-only
// get/set both public
public string Name { get; set; }
// get/set both private
private string SecretName { get; set; }
// public get => read-only
public string CalcName { get; private set; }
// public set => write-only
public string WriteOnlyName { private get; set; }
You could create a custom DTO as a view model for the POST operation on this controller. This would be additionally handy because you probably also don't want the client to supply the Id value either (I assume). Something like this:
public class PersonDTO
{
public string Name { get; set; }
public string Title { get; set; }
}
This would be the input for the controller action:
public void PostPerson(PersonDTO person)
{
// Upload person to database
}
Then in the code you'd create a new PersonModel to add to the data context. Something like:
using (var db = new MyDataContext())
{
var newPerson = new PersonModel
{
Name = person.Name,
Title = person.Title
};
db.Persons.Add(newPerson);
db.SaveChanges();
}
(Or perhaps create a kind of translation method on the DTO which returns an instance of the model, acting as a sort of factory method and putting the logic in the object rather than in the controller.) This way the client isn't providing an entire PersonModel instance, just an object which describes the creation of that instance. The GET operation can still return the full PersonModel.
When building an API (using WebAPI, for example) it can often be really useful to fine-tune the inputs and outputs like this. And such custom DTOs/ViewModels really come in handy, albeit at the cost of slightly more code by creating essentially a translation layer to the backing models.
One tool I've found particularly handy in determining where in the API I need to tweak things is when using Swagger to generate my API docs. Looking through the generated docs, I may notice something which I don't want to be exposed. This is an indicator that I need to customize that API endpoint a little more so that the resulting docs are a little cleaner.
Try adding the exclude attribute above the property
[Exclude]
public DateTime LastUpdated {get; set(}

Trying to use methods on values within get and set accessors gives a .Net framework error

I am trying to use some methods within set/get accessors on properties of classes generated by entity framework. Purpose is to encrypt the field before saving to DB and Decrypt the fields before reading from DB.I am doing it at the POCO class level so that the encryption-decryption happens while interacting with DB itself and I dont have to scriible through a lot of code. Is the POCO class the best interface to do that?
Code builds successfully, but I get a .Net Framework error when I execute it and the visual studio process is killed eventually.
I am not sure, I might be making some very basic error, but haven't been able to get down to it. Any suggestions will be greatly appreciated !
[Table("Users")]
public class User
{
[Key]
[Required]
public Int32 UserID { set; get; }
[Required]
[MaxLength(50)]
public String UserName // This is where I am using encryption decryption methods
{
set
{
this.UserName = NewEncryptionMethod(value);
}
get
{
return NewDecryptionMethod(this.UserName);
}
}
[Required]
public Int32 CustID { set; get; }
The reason that it's failing right now is because you're running into an infinite amount of calling the get part of UserName since you're trying to grab the value by trying to grab the value and decrypting it. This will cause an overflow error eventually.
Your solution to this is to leave the records in the database as they are, bring them over, and then use a ViewModel class that will contain the decrypted data. When you want to go back to the database, simply encrypt the value in the ViewModel and push that value to the database.
I don't say that the way you are doing the work is good , first thing i can say is that you have infinite recursive call.
Solution:
private string _username;
[Required]
[MaxLength(50)]
public String UserName // This is where I am using encryption decryption methods
{
set
{
_username = NewEncryptionMethod(value);
}
get
{
//you have to deal with a null username here is a bad but quick solution
_username = _username ?? string.Empty;
return NewDecryptionMethod(_username);
}
}
You can keep in UserName raw, insecure data. But make this property internal.
// map me
internal string UserName { get; set; }
And secure it using another property but public:
// don't map me
public string SecureUserName
{
get { return Encrypt(UserName);
set { UserName = Encrypt(value); }
}
Another way is to use a separate class for this purpose, say SecureUser or UserViewModel:
public class SecureUser
{
private readonly User _user;
public SecureUser(User user)
{
_user = user;
}
public string UserName
{
get { return Encrypt(_user.UserName);
set { _user.UserNAme = Decrypt(value); }
}
}

C# Complex Property Setter option

I have an ASP.Net MVC 5 (C#) application and I'm giving my users the ability to like posts and comments.
for this I have a Model called Likes with the following Properties:
public class Like
{
public Like()
{
this.CreatedUTC = System.DateTime.UtcNow;
this.isActive = true;
}
public long id { get; set; }
public string UserID { get; set; }
public bool isActive { get; set; }
public LikeType Type { get; set; }
public DateTime CreatedUTC { get; set; }
}
Type is an enum and it can be either Comments or Posts. I've also added the following Navigation Property to both Post Model and Comment Model:
public virtual ICollection<Like> Likes { get; set; }
My question is, can I have a setter function in the above line of code where it would automatically set Comments and Posts for the Like type? I know I can use the Value variable in the Setter but using Google I couldn't find how to use it for complex types as I have above (Like).
I'm sure this would be a better way of doing this than in the repository manually set that enum every-time I'm going to save a like.
UPDATE:
Seeing how we're starting a nice little conversation about this Questions, I will throw another alternative in the mix.
How about two Tables with a base class of Like, one CommentLikes and the other PostLikes Seeing that the size of this table will grow really fast, I'm thinking separating them would possibly be a good thing, Right?
I'd rather drop the "LikeType" and either have Comment and Post entity in the Like entity and distinguish by one of them being null or introduce two new entities
public class CommentLike
{
public Comment Comment { get; set; }
public Like Like { get; set; }
}
which holds a comment and a like and PostLike which holds a Post and a Like. The Comment then looks like this
public Comment
{
public virtual ICollection<CommentLike> { get; set; }
}
Another alternative is to create separate tables for comment and post likes. While what you ask for is definitely doable I would recommend more verbose but simpler solution which will result in code that is easier to maintain and has less bugs. Is there any specific reason you want to have a LikeType property?
I had same problem but didnt encounter an easy way.
class Post{
public virtual ICollection<Like> Likes {set;get;}
}
class Comment{
public virtual ICollection<Like> Likes {set;get;}
}
Then:
class Like{
//....
}
You dont need a bi-directional relationship. Do you have a case where you need to query Likes table? and if you do, you will have to manage parsing it as ENUM somewhere which can be an extension method.
EF will create Post_Id and Comment_Id implicityly in your table design. You wont be able to query it but you wont need to. IN my experience i never needed to.
My question is, can I have a setter function in the above line of code
where it would automatically set Comments and Posts for the Like type?
I assume you are using T4 template or the classes that were generated by EF are partialthen you can extend it by creating another partial class and a wrapper property for Likes
// Backing field
private ICollection<Like> _likesWrapper;
public ICollection<Like> LikesWrapper {
get {
// Lazy loading
if(_likes == null)
{
// Just create a new list or load it from the database.
_likes = new List<Like>();
}
return _likes;
}
set {
foreach(var val in value)
{
//Set the comment and posts
}
Likes = value;
}

DataAnnotations WriteOnly if property == null

I have the following property:
public virtual String Firstname { get; set; }
and i only want to be able to write to the field IF it is currently null (not set), it this possible to achieve through DataAnnotations?
Data annotations are metadata used for example for validation so you can create custom data annotation to validate property value but the validation cannot ensure that your property will not be set if it already has value. That is code which should be part of property's setter itself like:
private string _firstName;
public string FirstName
{
get
{
return _firstName;
}
set
{
if (_firstName != null) throw ...
_firstName = value;
}
}
If by data annotations you simply mean attributes then the answer is: It can be achieved with attributes BUT you need something which will implement some logic related to the attribute. This is usually done through Aspect oriented programming (AOP) where you will create marker attribute which will be used by some complex API. The API will based on that attribute wrap your class with custom code adding the if statement either at compile time (for example PostSharp) or at runtime (for example Unity, Spring.NET).
Another way to achive this, by me more elegant, do not implement set for the property, but only get
private string _firstName;
public string FirstName
{
get
{
return _firstName;
}
}
and have a function:
public void SetFirstName(string FirstName)
{
_firstName = FirstName;
}
So no exception, no return value handling. You have one property the only retrieve value, and one function, or constructor (why not, depends on your architecture, it's hard to deduct from post) that initialized it only once.
By me the API of your object will be more clear and straightforward in this way.
Regards.
There is also a specific DataAnnotation syntax to achieve this:
[DisplayFormat(NullDisplayText = "some string")]
public virtual String Firstname { get; set; }

IDataErrorInfo with complex types

I have an Address object defined simply as follows:
public class Address
{
public string StreetNumber { get; set; }
public string StreetAddress { get; set; }
public string City { get; set; }
public string PostalCode { get; set; }
}
Fairly simple. On the advice an answer to another question I asked, I am referring to this blog post when databinding my UI to an object of type Person (which contains an Address MailingAddress field).
The problem is that the IDataError interface method isn't validating any of the properties of the Address type.
public string this[string columnName]
{
get
{
string result = null;
// the following works fine
if(columnName == "FirstName")
{
if (string.IsNullOrEmpty(this.FirstName))
result = "First name cannot be blank.";
}
// the following does not run
// mostly because I don't know what the columnName should be
else if (columnName == "NotSureWhatToPutHere")
{
if (!Util.IsValidPostalCode(this.MailingAddress.PostalCode))
result = "Postal code is not in a know format.";
}
return result;
}
}
So, obviously I don't know what the columnName will be... I've stepped through it and it has never been anything other than any of the public properties (of intrinsic types). I've even tried running and breaking on a statement like:
if (columnName.Contains("Mailing") || columnName.Contains("Postal"))
System.Windows.Forms.MessageBox.Show(columnName);
All to no avail.
Is there something I'm missing?
You need to define IErrorInfo on all the classes that you want to supply error messages for.
Take a look at my answer here.
This explains how to use a modelbinder to add 'class-level' checking of your model without having to use IDataError - which as you have seen here can be quite clumsy. It still lets you use [Required] attributes or any other custom validation attributes you have, but lets you add or remove individual model errors. For more on how to use data annotations I highly recommend this post from Scott Gu.

Categories

Resources