Implementing Audit trail c# - c#

i implemented an audit log for every action made in the server (added, modified and delete). The problem occurs in the modified, because i audit every property which was modified, but some properties i don`t want to audit. Ex: Timestamp, or others.
This is what i did, and works fine:
1) I made another SaveChanges() method into DBContext
2)
if (dbEntity.State == EntityState.Modified)
{
foreach (string propertyName in dbEntity.OriginalValues.PropertyNames)
{
if (!Equals(dbEntity.OriginalValues.GetValue<object>(propertyName), dbEntity.CurrentValues.GetValue<object>(propertyName)))
{
var log = new AuditLogDetailEntity()
{
Timestamp = timestamp,
Type = "M", // Modified
EntityName = tableName1,
PrimaryKeyValue = Convert.ToInt32(dbEntity.CurrentValues.GetValue<object>(primaryKeyName)),
PropertyName = propertyName,
OldValue = dbEntity.OriginalValues.GetValue<object>(propertyName) == null ? null : dbEntity.OriginalValues.GetValue<object>(propertyName).ToString(),
NewValue = dbEntity.CurrentValues.GetValue<object>(propertyName) == null ? null : dbEntity.CurrentValues.GetValue<object>(propertyName).ToString()
};
changesCollection.Add(log);
}
}
}`
This is an extract code, not all the funcion.
I could make a validation inside, asking for that fields I don`t want to audit, but, Is there a more thorough way of doing it? Maybe adding some dataannotations in the classes, or something else..
thanks.

You can use the [System.ComponentModel.DataAnnotations.Schema.NotMapped] attribute for that.

You could create a custom attribute
[AttributeUsage(AttributeTargets.Property, AllowMultiple = false)]
public class UnMappedAttribute : Attribute
{
}
And then check if each property has it
foreach (string propertyName in dbEntity.OriginalValues.PropertyNames)
{
if(!dbEntity.Entity.GetType().GetCustomAttributes(typeof(UnMappedAttribute), true).Any())
{
continue;
}
if (!Equals(dbEntity.OriginalValues.GetValue<object>(propertyName), dbEntity.CurrentValues.GetValue<object>(propertyName)))
{
//.....
}
}

Related

Change property name for client output

I am writing some code that will send an email with details of what is inside the properties of a class.
Instead of hard coding the rows with the properties, I thought it was best to do this via reflection
var builder = new StringBuilder();
Type type = obj.GetType();
PropertyInfo[] properties = type.GetProperties();
foreach (PropertyInfo property in properties)
{
if (property.GetValue(obj, null) != null)
{
builder.AppendLine("<tr>");
builder.AppendLine("<td>");
builder.AppendLine("<b> " + property.Name + " </b>");
builder.AppendLine("</td>");
builder.AppendLine("<td>");
builder.AppendLine(property.GetValue(obj, null).ToString());
builder.AppendLine("</td>");
builder.AppendLine("</tr>");
}
}
Which also helps leave out all the properties that hasn't been set which again helps to reduce code.
However property.Name quite rightly outputs the name of the property in its current form
public string PropertyA { get; set; }
So the Email would look like
PropertyA : 123
Which doesnt look friendly to the user. So is there a way I can change the property name to display something different?
I have tried
[DisplayName("Property A")]
public string PropertyA { get; set; }
which should look like in the email:
Property A : 123
But to no prevail.... Is there anything out there to help on the road of the logic I am going down?
Thanks
You need to find the attribute and extract the Name value:
var displayNameAttribute = property.GetCustomAttributes
(typeof(DisplayNameAttribute), false)
.FirstOrDefault() as DisplayNameAttribute;
string displayName = displayNameAttribute != null
? displayNameAttribute.DisplayName
: property.Name;
You need to get DisplayNameAttribute of your property and then get it's Name:
var attribute = property.GetCustomAttribute<DisplayNameAttribute>();
if(attribute != null)
{
var displayName = attribute.Name;
}

How to get names of all required fields in new row with EF?

I'm using EF(db first) and trying to add new row in table using the next code:
var user = new User();
//Some logic to fill the properties
context.Users.AddObject(user);
context.SaveChanges();
Before saving changes on EF i want to verify that all required (not null and with no default value) properties are filled. How can i get all such fields?
I've tried few ways, but can't achieve needed result. The last try was like that:
var resList = new List<PropertyInfo>();
var properties = type.GetProperties(BindingFlags.DeclaredOnly |
BindingFlags.Public |
BindingFlags.Instance).Where(p => !p.PropertyType.IsGenericType);
foreach (var propertyInfo in properties)
{
var edmScalarProperty =
propertyInfo.CustomAttributes.FirstOrDefault(
x => x.AttributeType == typeof (EdmScalarPropertyAttribute));
var isNullable = true;
if (edmScalarProperty != null)
{
var arg = edmScalarProperty.NamedArguments.FirstOrDefault(x => x.MemberName == "IsNullable");
if (arg != null)
{
isNullable = (bool) arg.TypedValue.Value;
}
}
if (!isNullable)
{
resList.Add(propertyInfo);
}
}
return resList;
Create a constructor with the required fields as parameters.
I always separate my domain objects from my EF objects (DTO objects). The domain object has only one constructor with the required fields. When I want to save these objects I convert them to DTO objects.
Have you looked at all into DataAnnotations for your model classes? Utilizing these (and using a separate object from the one EF creates for you) you can get pretty significant validation built into your models from the model level. Additionally, as L01NL pointed out, you can have your constructor take in parameters that require data.
Lots of information on Model and Validation can be found, one such example is:
http://msdn.microsoft.com/en-us/library/dd410405(v=vs.100).aspx
(look through this main section and its subsections)
using System.ComponentModel.DataAnnotations
public class Foo
{
public Guid Id { get; private set; }
[StringLength(50),Required]
public string FooName { get; private set; }
[Required]
public int Age { get; private set; }
// etc props
public Foo(string fooName, int age)
{
if (string.IsNullOrEmpty(fooName))
throw new ArgumentException("FooName cannot be null or empty"); // note there is also a "minimum length" data annotation to avoid doing something like this, was just using this as an example.
this.Id = Guid.NewGuid();
this.FooName = fooName;
this.Age = age;
}
}
public class YourController
{
[HttpPost]
public ActionResult Add(Foo foo)
{
if (!ModelState.IsValid)
// return - validation warnings, etc
// Add information to persistence
// return successful add?
}
}

asp.net mvc web api partial update with OData Patch

I am using HttpPatch to partially update an object. To get that working I am using Delta and Patch method from OData (mentioned here: What's the currently recommended way of performing partial updates with Web API?). Everything seems to be working fine but noticed that mapper is case sensitive; when the following object is passed the properties are getting updated values:
{
"Title" : "New title goes here",
"ShortDescription" : "New text goes here"
}
But when I pass the same object with lower or camel-case properties, Patch doesn't work - new value is not going through, so it looks like there is a problem with deserialisation and properties mapping, ie: "shortDescription" to "ShortDescription".
Is there a config section that will ignore case sensitivity using Patch?
FYI:
On output I have camel-case properties (following REST best practices) using the following formatter:
//formatting
JsonSerializerSettings jss = new JsonSerializerSettings();
jss.ContractResolver = new CamelCasePropertyNamesContractResolver();
config.Formatters.JsonFormatter.SerializerSettings = jss;
//sample output
{
"title" : "First",
"shortDescription" : "First post!"
}
My model classes however are follwing C#/.NET formatting conventions:
public class Entry {
public string Title { get; set;}
public string ShortDescription { get; set;}
//rest of the code omitted
}
Short answer, No there is no config option to undo the case sensitiveness (as far as i know)
Long answer: I had the same problem as you today, and this is how i worked around it.
I found it incredibly annoying that it had to be case sensitive, thus i decided to do away with the whole oData part, since it is a huge library that we are abusing....
An example of this implementation can be found at my github github
I decided to implement my own patch method, since that is the muscle that we are actually lacking. I created the following abstract class:
public abstract class MyModel
{
public void Patch(Object u)
{
var props = from p in this.GetType().GetProperties()
let attr = p.GetCustomAttribute(typeof(NotPatchableAttribute))
where attr == null
select p;
foreach (var prop in props)
{
var val = prop.GetValue(this, null);
if (val != null)
prop.SetValue(u, val);
}
}
}
Then i make all my model classes inherit from *MyModel*. note the line where i use *let*, i will excplain that later. So now you can remove the Delta from you controller action, and just make it Entry again, as with the put method. e.g.
public IHttpActionResult PatchUser(int id, Entry newEntry)
You can still use the patch method the way you used to:
var entry = dbContext.Entries.SingleOrDefault(p => p.ID == id);
newEntry.Patch(entry);
dbContext.SaveChanges();
Now, let's get back to the line
let attr = p.GetCustomAttribute(typeof(NotPatchableAttribute))
I found it a security risk that just any property would be able to be updated with a patch request. For example, you might now want the an ID to be changeble by the patch. I created a custom attribute to decorate my properties with. the NotPatchable attribute:
public class NotPatchableAttribute : Attribute {}
You can use it just like any other attribute:
public class User : MyModel
{
[NotPatchable]
public int ID { get; set; }
[NotPatchable]
public bool Deleted { get; set; }
public string FirstName { get; set; }
}
This in this call the Deleted and ID properties cannot be changed though the patch method.
I hope this solve it for you as well. Do not hesitate to leave a comment if you have any questions.
I added a screenshot of me inspecting the props in a new mvc 5 project. As you can see the Result view is populated with the Title and ShortDescription.
It can be done quite easily with a custom contract resolver that inherits CamelCasePropertyNamesContractResolver and implementing CreateContract method that look at concrete type for delta and gets the actual property name instead of using the one that comes from json. Abstract is below:
public class DeltaContractResolver : CamelCasePropertyNamesContractResolver
{
protected override JsonContract CreateContract(Type objectType)
{
// This class special cases the JsonContract for just the Delta<T> class. All other types should function
// as usual.
if (objectType.IsGenericType &&
objectType.GetGenericTypeDefinition() == typeof(Delta<>) &&
objectType.GetGenericArguments().Length == 1)
{
var contract = CreateDynamicContract(objectType);
contract.Properties.Clear();
var underlyingContract = CreateObjectContract(objectType.GetGenericArguments()[0]);
var underlyingProperties =
underlyingContract.CreatedType.GetProperties(BindingFlags.Public | BindingFlags.Instance);
foreach (var property in underlyingContract.Properties)
{
property.DeclaringType = objectType;
property.ValueProvider = new DynamicObjectValueProvider()
{
PropertyName = this.ResolveName(underlyingProperties, property.PropertyName),
};
contract.Properties.Add(property);
}
return contract;
}
return base.CreateContract(objectType);
}
private string ResolveName(PropertyInfo[] properties, string propertyName)
{
var prop = properties.SingleOrDefault(p => p.Name.Equals(propertyName, StringComparison.OrdinalIgnoreCase));
if (prop != null)
{
return prop.Name;
}
return propertyName;
}
}

Change custom attribute's parameter at runtime

I need change attribute's parameter during runtime. I simplified my problem to simple example.
Attribute class:
[AttributeUsage(AttributeTargets.Property)]
public class MyAttribute : Attribute
{
public string Name { get; set; }
}
Simple entity which has decorated properties with attributes:
public class MyEntity
{
[MyAttribute(Name="OldValue1")]
public string Data1{ get; set; }
[MyAttribute(Name = "OldValue2")]
public string Data2 { get; set; }
}
I created instance of class MyEntity. I can change value of object's properties, but I can't change value of attribute’s property Name on object entity. Is it possible?
Value of property on object entity I can change with this part of code:
entityProp.SetValue(entity,"NewData",null);
but I don't how change value of attribute's property Name on object entity
This does not work:
attProp.SetValue(attribute,"NewData",null);
Value of property Name is still original.
Here is all test code.
[TestMethod]
public void Test()
{
var entity = new MyEntity
{
Data1 = "OldData",
Data2 = "OldData"
};
PropertyInfo[] entityProps = entity.GetType().GetProperties();
foreach (var entityProp in entityProps)
{
var attribute = Attribute.GetCustomAttribute(entityProp, typeof (MyAttribute)) as MyAttribute;
if (attribute != null)
{
//get attribute's property NAME
PropertyInfo attProp= attribute.GetType().GetProperty("Name");
//get entity property value
var propertyValue = entityProp.GetValue(entity, null);
//get attribute’s property NAME value
var atributeNameValue = attProp.GetValue(entity, null);
TestContext.WriteLine(string.Format("property name:{0} property value: {1} : atribute name value: {2}\n",
entityProp.Name, propertyValue, atributeNameValue));
//change values
entityProp.SetValue(entity,"NewData",null);
//how can I change value of property Name on object entity ?
attProp.SetValue(attribute,"NewData",null);
}
}
TestContext.WriteLine(string.Format("After change\n"));
foreach (var entityProp in entityProps)
{
var attribute = Attribute.GetCustomAttribute(entityProp, typeof(MyAttribute)) as MyAttribute;
if (attribute != null)
{
PropertyInfo attProp = attribute.GetType().GetProperty("Name");
var propertyValue = entityProp.GetValue(entity, null);
var atributeNameValue = attProp.GetValue(entity, null);
TestContext.WriteLine(string.Format("property name:{0} property value: {1} : atribute name value: {2}\n",
entityProp.Name, propertyValue, atributeNameValue));
}
}
}
EDITED: I delete original post and added very simple clear sample. Sorry
You cannot change attributes at runtime. They are embedded into the metadata of the assembly. Your method is changing the internal state of a particular instance; but when you load the attribute again, you are getting a different instance.
This is not possible with reflection, as (as already noted) the metadata is fixed. It is, however, partly possible with TypeDescriptor, which allows adding and replacing of attributes at runtime, and providing complete alternative models (TypeDescriptionProvider, etc). This approach will not be respected by any code that uses reflection, but any code using TypeDescriptor (most typically, data-binding and other UI code) will notice the changes.
Note TypeDescriptor only really works with one of each attribute-type per typ/member; multi-instance attributes are not well supported.

ActiveRecord create dynamic conditions in find method

I'm creating a C# application using ActiveRecord as my datalayer. I've created a business class named UserGrabber which is supposed to find users by using it's properties to send as filters into a ActiveRecord.Find Method.
For example a User class. The client needs to find all users that have active directory name starting with "anna" and with SSN containing 4229. Then I would like to do
UserGrabber grabber = new UserGrabber();
grabber.ADName = "anna";
grabber.SSN = "4229";
grabber.Grab();
foreach(User user in grabber.Users)
{
Console.WriteLine(user.FullName);
}
The trick is that I don't have to send information to UserGrabber unless I want to filter by it, I could have send just in grabber.ADName, then the SSN would not be filtered by.
The problem is I can't seem to grasp how to do this in ActiveRecord. Maybe I could use the ExecuteQuery(Castle.ActiveRecord.IActiveRecordQuery) or FindAll(NHibernate.Criterion.ICriterion) ?
Alright, I have figured this out :)
I started by creating class named ActiveRecordCustomBase with the following implementation
public class ActiveRecordCustomBase<T> : ActiveRecordBase<T>
{
public static string GetPropertyColumnName(string propertyName)
{
System.Reflection.PropertyInfo property = typeof(T).GetProperty(propertyName);
object[] attributes = property.GetCustomAttributes(false);
if (attributes != null)
{
foreach (object attr in attributes)
{
if (attr is PrimaryKeyAttribute)
{
return ((PrimaryKeyAttribute)attr).Column;
}
else if (attr is PropertyAttribute)
{
return ((PropertyAttribute)attr).Column;
}
}
}
return null;
}
}
By letting all my ActiveRecord classes inherit from ActiveRecordCustomBase, it allows me to use the function GetPropertyColumnName so that the column mapping will be in 1 location (therefore not vialoting the DRY principal).
Next I created a finder method in my business class which looks like this:
UserCriteria userCriteria = (UserCriteria)criteria;
if (userCriteria.UserId != 0)
{
UserDAO userDAO = UserDAO.TryFind(userCriteria.UserId);
}
else if (userCriteria.MasterDataId != 0)
{
UserDAO userDAO = UserDAO.FindOne(Restrictions.Eq(UserDAO.GetPropertyColumnName("ExternalId1"), userCriteria.MasterDataId));
}
else if (!userCriteria.ADName.Equals(string.Empty))
{
UserDAO userDAO = UserDAO.FindOne(Restrictions.Eq(UserDAO.GetPropertyColumnName("ADName"), userCriteria.ADName.ToLower()));
}
That was enough to get the dynamic behaviour I was seeking

Categories

Resources