how to implement custom field with Audit.Net entityframework - c#

I'm new to Audit.net. I successfully configure it in my project.
But now, i need to extend it to save extra information like requesterID and a comment write on the view page.
my case :
Entity mapped to Entity_AT
Entity {
[Key]
int ID
string label
}
Entity_AT {
[Key]
int ATID
int ID
string label
int ATFlag
datetime ATCreationDate
string RequesterID
string ATComment
}
public void Add(TEntity obj, string RequesterId)
{
_dbSet.Add(obj);
_context.SaveChanges();
}
[AuditDbContext(Mode = AuditOptionMode.OptOut, IncludeEntityObjects = false, AuditEventType = "{database}_{context}")]
public class MyDBContext : AuditIdentityDbContext<ApplicationUser>
{
}
Audit.Core.Configuration.DataProvider = new EntityFrameworkDataProvider()
{
AuditTypeMapper = t => t == typeof(Entity) ? typeof(Entity_AT) : null,
AuditEntityAction = (evt, entry, auditEntity) =>
{
var a = (dynamic)auditEntity;
a.ATCreationDate = DateTime.UtcNow;
a.ATFlag = (entry.Action == "Insert") ? 1 : (entry.Action == "Update") ? 2 : (entry.Action == "Delete") ? 3 : 0;
return true; // return false to ignore the audit
}
};
I tried :
_context.AddAuditCustomField("RequesterId", requesterId);
_context.AddAuditCustomField("ATComment", "pippo");
_dbSet.Add(obj);
_context.SaveChanges();
but no results

You have added the "RequesterId" custom field to the AuditEvent, but you are not mapping it to the AuditEntity property.
You should do the following on the entity action:
Audit.Core.Configuration.DataProvider = new EntityFrameworkDataProvider()
{
AuditTypeMapper = t => t == typeof(Entity) ? typeof(Entity_AT) : null,
AuditEntityAction = (evt, entry, auditEntity) =>
{
var a = (dynamic)auditEntity;
a.ATCreationDate = ...;
a.ATFlag = ...;
a.RequesterID = evt.CustomFields["RequesterId"] as string;
a.ATComment = evt.CustomFields["ATComment"] as string;
return true;
}
};
Note you can alternatively use the Fluent API to configure the data provider with a cleaner syntax:
Audit.Core.Configuration.Setup()
.UseEntityFramework(_ => _
.AuditTypeExplicitMapper(map => map
.Map<Entity, Entity_AT>((evt, entry, auditEntity) =>
{
auditEntity.ATCreationDate = ...;
auditEntity.ATFlag = ...;
auditEntity.RequesterID = evt.CustomFields["RequesterId"] as string;
auditEntity.ATComment = evt.CustomFields["ATComment"] as string;
})));

Related

Best way to append query string parameter to URL from object

I have query string class.
public class PagingModel
{
public int PageNumber { get; set; } = 1;
public string Filter { get; set; } = "text";
}
string url = "Menu/GetMenus";
I have to generate the URI with a query string based on an object in ASP.NET Core 5 preview. Is there any built in query helper?.
Required output:
/Menu/GetMenus?PageNumber=3&Filter=text
MVC Controller:
public async Task<IActionResult> index_partial([FromQuery] PagingModel paging)
{
var data = await _apiService.GetMenusAsync(paging);
return PartialView("_IndexPartial", data);
}
Service:
public async Task<PagedList<MenuModel>> GetMenusAsync(PagingModel paging)
{
string Requiredurl = "Menu/GetMenus?page="+ paging.PageNumber;
}
I got this extension method.. No need to generate query string manually.Only class object we need to pass. i thought some one else can use the same thing ...
public static string AppendObjectToQueryString(string uri, object requestObject)
{
var type = requestObject.GetType();
var data = type.GetProperties(BindingFlags.Public | BindingFlags.Instance)
.ToDictionary
(
p => p.Name,
p => p.GetValue(requestObject)
);
foreach (var d in data)
{
if (d.Value == null)
{
continue;
}
if ((d.Value as string == null) && d.Value is IEnumerable enumerable)
{
foreach (var value in enumerable)
{
uri = QueryHelpers.AddQueryString(uri, d.Key, value.ToString());
}
}
else
{
uri = QueryHelpers.AddQueryString(uri, d.Key, d.Value.ToString());
}
}
return uri;
}
Ex: In my case i called this way.
string uri = "Menu/GetMenus";
string full_uri = QueryStringExtension.AppendObjectToQueryString(uri, paging);
With a Query String this simple I would just do
PagingModel qsData = new PagingModel();
//set qsData properties as needed
string urlWithQueryString = $"/Menu/GetMenus?{nameof(PagingModel.PageNumber)}={qsData.PageNumber}&nameof(PagingModel.Filter)}={qsData.Filter}";
However more standard is to do something like
string urlWithQueryString = this.Url.Action("GetMenus", "Menu", new PagingModel { PageNumber = 3, Filter = "text" }, this.Request.Url.Scheme);
But best solution depends on your specific case - can you add your action method definition for GetMenus ?
Update for your additional code :
Seeing as looks like you want to generate the url inside the service I would simply do this :
public async Task<PagedList<MenuModel>> GetMenusAsync(PagingModel paging)
{
string Requiredurl = $"/Menu/GetMenus?{nameof(PagingModel.PageNumber)}={paging.PageNumber}&nameof(PagingModel.Filter)}={paging.Filter}";
}

Determine Hierarchy Integer from Parent Field C#

I need to determine the hierarchy level to display a tree, I don't need to link relationships at the moment, I have a list of objects as follows:
public class ObjectData
{
public string ID;
public string hierarchyParent;
public int hierarchyLevel;
}
I need to set the hierarchyLevel integer based on its row level. The hierarchyParent var contains the ID of its parent. I don't know how wide each column would be nor how many rows, so it needs to be dynamic with the hierarchy level integer either ascending or descending. So far, I have been able to determine the top row but am unsure how to continue, any help would be appreciated! So far:
List<ObjectData> Sort(List<ObjectData> objectToBeSorted){
List<ObjectData> returnlist = new List<ObjectData>();
string topObject = null;
foreach(ObjectData obj in objectToBeSorted)
{
if(obj.hierarchyParent == null){
topObject = obj.ID;
obj.hierarchyLevel = 1;
}
}
foreach(ObjectData obj in objectToBeSorted)
{
if(obj.hierarchyParent == topObject){
}
}
return returnlist;
}
Here's a quick try with sample data and recursive calls :
The useful part is is in AssignChild method.
public class ObjectData
{
public string ID;
public string hierarchyParent;
public int hierarchyLevel;
}
void Main()
{
var objects = new List<ObjectData>() {
new ObjectData() { ID = "Obj12", hierarchyParent = null },
new ObjectData() { ID = "Obj5", hierarchyParent = "Obj12" },
new ObjectData() { ID = "Obj9", hierarchyParent = "Obj12" },
new ObjectData() { ID = "Obj7", hierarchyParent = "Obj5" },
new ObjectData() { ID = "Obj99", hierarchyParent = "Obj58" },
new ObjectData() { ID = "Obj58", hierarchyParent = "Obj5" } };
ObjectData top = objects.Find(p => p.hierarchyParent == null);
top.hierarchyLevel = 1;
AssignChild(objects, top);
objects.Dump();
}
void AssignChild(List<ObjectData> all, ObjectData parent)
{
var child = all.FindAll(o => o.hierarchyParent == parent.ID);
child.ForEach(c => { c.hierarchyLevel = parent.hierarchyLevel +1; AssignChild(all, c); });
}
It can probably be optimized but it should work.
I suggest doing something like this:
public int GetHierarchyLevel(ObjectData obj, IEnumerable<ObjectData> allObjects)
{
if(obj.hierarchyParent == null)
return 1;
else
return 1 + GetHierarchyLevel(allObjects.First(o=>o.ID == obj.hierarchyParent));
}
Of course, you should integrate this into your classes so that you can possibly replace the arguments by class members. Also, please notice that some error checking may be required. It is just meant to give you an idea of an algorithm.
For performance, I suggest a caching mechanism. Like initializing hierarchyLevel to -1 and using the following modification:
public int GetHierarchyLevel(ObjectData obj, IEnumerable<ObjectData> allObjects)
{
if (obj.hierarchyLevel != -1)
return obj.hierarchyLevel;
if(obj.hierarchyParent == null)
return 1;
else
return 1 + GetHierarchyLevel(allObjects.First(o=>o.ID == obj.hierarchyParent));
}
Of course, this would require invalidating all cached results when you want to recalculate after a change in the structure of your hierarchy.

Check EF before changing view or URL MVC4

I need check my EF or values of certains data before changing view or url inside the app
I have a view where process import information to other wiew under controller, i need to check values before user changing to other view or want try other process inside the view (import information)
i will try under my button inside the view but is the user want change to other view the process not work...
The process under the button is this:
Controller:
public ActionResult Index(int? page, string filter, int id = 0)
{
ViewBag.OrderPurchaseID = id;
var check_import = db.OrderPurchaseDetails.Where(o => o.OrderPurchaseID == id && o.QtyTraslate > 0).ToList();
if (check_import.Count() > 0)
{
TempData["ErrorMessage"] = "You have articles pending to import, check or delete them";
return RedirectToAction("Edit", "OrderPurchase", new { id = id });
}
#region remember filter stuff
if (filter == "clear")
{
Session["Text"] = null;
Session["From"] = null;
Session["To"] = null;
}
else
{
if ((Session["Text"] != null) || (Session["From"] != null) || (Session["To"] != null))
{
return RedirectToAction("Search", new { text = Session["Text"], from = Session["From"], to = Session["To"] });
}
}
#endregion
var orderpurchases = db.OrderPurchases.Include(o => o.Provider);
int currentPageIndex = page.HasValue ? page.Value - 1 : 0;
return View(orderpurchases.OrderByDescending(p => p.TimeStamp).ToPagedList(currentPageIndex, defaultPageSize));
}
View:
#Html.ActionLink("List", "Index", new { id = Model.OrderPurchaseID }, new { #class = "btn" })
I need replicate this method to global level, if is possible of course...
Thanks for your help.
i use LogAttribute to check data in my EF from import data, thanks to Yuliam and Lee Winter for the help and bring me a solution global level.
public class LogAttribute : ActionFilterAttribute
{
private dbcAmerica db = new dbcAmerica();
public override void OnActionExecuting(ActionExecutingContext filterContext)
{
int data = Convert.ToInt32(filterContext.Controller.TempData["id"]);
var checkIn = db.OrderPurchaseDetails.Where(o => o.QtyTraslate > 0 && o.OrderPurchaseID == data).ToList();
if (checkIn.Count() > 0)
{
filterContext.Result = new RedirectToRouteResult(
new RouteValueDictionary
{
{ "action", "Edit" },
{ "controller", "OrderPurchase" },
{ "id", data},
});
}
}
public override void OnResultExecuted(ResultExecutedContext filterContext)
{
// ... log stuff after execution
}
}

Entity framework error as"New transaction is not allowed because there are other threads running in the session

We are using entity framework codefirst approach
I am new to entity framework and I am facing error while trying to do "New transaction is not allowed because there are other threads running in the session.
public class DatabaseBackup : IDataBackup
{
private readonly IMonarchDbContext m_db;
public DatabaseBackup(IMonarchDbContext podb)
{
if (podb == null)
throw new ArgumentNullException("podb");
m_db = podb;
}
public DBBackupHistory GetLatestBackupHistory(DBBackupFrequency backupFrequency = DBBackupFrequency.Periodic)
{
DBBackupHistory result = null;
// get the backup history of the given backuptype and populate the objects
var configId = m_db.DBBackupConfigurations.Where(c => c.ScheduleType == (int)backupFrequency && c.BackupStatus == 1).Distinct().Select(c => c.ConfigurationId).DefaultIfEmpty(-1).First();
if (configId > 0)
{
result = m_db.DBBackupHistorys.Where(b => b.Status == 1 && b.ConfigurationId == configId).OrderByDescending(lb => lb.BackupDatetime).FirstOrDefault();
}
return result;
}
public IEnumerable<DBBackupConfiguration> GetAllConfiguration()
{
var result = m_db.DBBackupConfigurations.Where(c => c.BackupStatus == 1).OrderByDescending(c => c.ConfigurationId);
return result;
}
public void Backup(DBBackupConfiguration config, int fileIndex)
{
Console.WriteLine("Running DB Backup type {0} to device {1}", (DBBackupType)config.BackupType, fileIndex);
m_db.StoredProc.SPBackup(config, fileIndex);
}
I am calling the below methods in another class as follows
private readonly IDataBackup m_dataBackup;
public int PerformBackup(int defaultPollIntervalInMinutes = 15)
{
// polling interval in Minutes
int pollInterval = defaultPollIntervalInMinutes;
int fileIndex = getCurrentDumpFileIndex();
// check for the backup configuration
var configurations = m_dataBackup.GetAllConfiguration();
foreach (var config in configurations)
{
var lastBackup = m_dataBackup.GetLatestBackupHistory(DBBackupFrequency.Weekly);
if (lastBackup == null)
{
m_dataBackup.Backup(config, fileIndex + 1);
break;
}
Here is the Db Context class is as below
public class MonarchDbContext:DbContext,IMonarchDbContext
{
private IStoredProcedure m_storedProc;
private static object m_dbIntializerSet;
public MonarchDbContext(string nameOrConnectionString)
: base( nameOrConnectionString )
{
//-- Set the DB initializer only once.
System.Threading.LazyInitializer.EnsureInitialized( ref m_dbIntializerSet,()=>{
Database.SetInitializer<MonarchDbContext>(null);
//-- Give debug builds a chance to overwrite the above.
_SetInitializerForDebugBuilds();
return new object();
});
Configuration.LazyLoadingEnabled = false;
Configuration.ProxyCreationEnabled = false;
var csb = new SqlConnectionStringBuilder( this.Database.Connection.ConnectionString );
csb.MultipleActiveResultSets = true;
this.Database.Connection.ConnectionString = csb.ToString();
var objectContext = ( this as IObjectContextAdapter ).ObjectContext;
objectContext.CommandTimeout = 3600;
}
#region Public "Tables"
public IDbSet<DBBackupConfiguration> DBBackupConfigurations { get; set; }
public IDbSet<DBBackupHistory> DBBackupHistorys { get; set; }
public IStoredProcedure StoredProc
{
get
{
return System.Threading.LazyInitializer.EnsureInitialized(ref m_storedProc, () => new BackupStoredProc(this.Database));
}
}
#endregion
please let me know how can i solve the issue.
I found the issue
I need to add toList() at the end of the Linq code and it just worked for me.
public IEnumerable<DBBackupConfiguration> GetAllConfiguration()
{
var result = m_db.DBBackupConfigurations.Where(c => c.BackupStatus == 1).OrderByDescending(c => c.ConfigurationId).ToList();
return result;
}
Just add the List to Ienumerbale types

Find all classes which derive from a specific base class and add them to the registry

I have a base class called BaseStatus which looks like this:
public class BaseStatus
{
public int UnitId { get; protected set; }
public UInt16 StatusValue { get; protected set; }
public string StatusCode { get; protected set; }
public string StatusDescription { get; protected set; }
public BaseStatus()
{
this.UnitId = -1;
this.StatusValue = 0;
this.StatusCode = null;
this.StatusDescription = null;
}
}
Furthermore i have two or more other base classes which derive from BaseStatus and define a other unit id. For example the two classes
public class BaseGlobalStatus : BaseStatus
{
public BaseGlobalStatus()
{
base.UnitId = -1;
}
}
public class BaseGcmGdmStatus : BaseStatus
{
public BaseGcmGdmStatus()
{
base.UnitId = 2;
}
}
public class BaseCcuStatus : BaseStatus
{
public BaseCcuStatus()
{
base.UnitId = 1;
}
}
The Background is that i want to derive from for example BaseCcuStatus and have the correct UnitId in the derived class.
Now i define my correct status classes for example:
public class StatStErrDefinition : BaseGlobalStatus
{
public StatStErrDefinition()
: base()
{
base.StatusDescription = "Kommando nicht zulässig, unit im state ERROR";
base.StatusCode = "STAT_ST_ERR";
base.StatusValue = 3;
}
}
public class GcmStErrDefinition : BaseGcmGdmStatus
{
public GcmStErrDefinition()
: base()
{
base.StatusDescription = "Kommando nicht zulässig, unit im state ERROR";
base.StatusCode = "STAT_ST_ERR";
base.StatusValue = 3;
}
}
public class CcuStErrDefinition : BaseCcuStatus
{
public CcuStErrDefinition()
: base()
{
base.StatusDescription = "Kommando nicht zulässig, unit im state ERROR";
base.StatusCode = "STAT_ST_ERR";
base.StatusValue = 3;
}
}
For my understading, the three classes StatStErrDefinition, GcmStErrDefinition and CcuStErrDefinition should have the UnitId which is set in the derived BaseClass?
Now that i have defined my three Status Classes i want to get them into a registry. Currently im using this piece of code to try get them. Problem is that the result has no items.
registry = new StatusDictionary<UInt16, BaseStatus>();
var unitStatus = typeof(BaseStatus)
.Assembly.GetTypes()
.Where(x => x.BaseType == typeof(BaseStatus))
.Select(x => new
{
StatusType = x,
UnitId = x.GetProperty("UnitId", BindingFlags.Public)
StatVal = x.GetProperty("StatusValue", BindingFlags.Public)
}
)
.Where(x => x.StatVal != null && x.UnitId != null)
.Select(x => new
{
UnitId = (int)x.UnitId.GetValue(null, null),
StatusValue = (UInt16)x.StatVal.GetValue(null, null),
Factory = (Func<BaseStatus>)(() => ((BaseStatus)Activator.CreateInstance(x.StatusType)))
});
try
{
foreach (var status in unitStatus)
{
if (status.UnitId == unitId
|| status.UnitId < 0)
registry.Register(status.StatusValue, status.Factory);
}
}
catch (Exception ex)
{
string temp = ex.Message;
}
After the LINQ expression the var unitStatus is empty...
Later, the registry call looks like that to get the specific class but that is unimportant at this point:
stat = StatusContainer.GetRegistry(this.unitTypeId).GetInstance(this.StatusValue);
For information:
I want to get the status class which should be in the registry by the unittypeid and the specific status value.
Currently my registry method does not work because he is not able to find any class. So there has to be a mistake somewhere. Thanks in advance
#Update 1
I changed my functionality a little bit:
registry = new StatusDictionary<UInt16, BaseStatus>();
//get all types of cucrent assembly
var allAssemblyTypes = Assembly.GetCallingAssembly().GetTypes();
//get all types from base status
var baseStatusTypes = allAssemblyTypes.Where(x => x.BaseType == typeof(BaseStatus));
//Place all concrete types in the foundtypes
List<Type> foundTypes = new List<Type>();
foreach (Type item in baseStatusTypes)
{
var temp = allAssemblyTypes.Where(x => x.BaseType == item)
.Select(x => new
{
StatusType = x,
UnitId = x.GetProperty("UnitId", BindingFlags.Public),
StatVal = x.GetProperty("StatusValue", BindingFlags.Public),
}
);
}
Temp contains now the correct type.
Problem is that if temp is type of StatStErrDefinition the StatusValue and UnitId Property is null.
The fact is that these members are instance members. Is there a way to get the values out of them?
First thing first : your LINQ query is pretty long.
divide it in different step and store them in different variables (or make properties out of them, whatever you prefer)
This is
easy to read / maintain
easy to debug
With this given I think you are able to solve your problem :)
To check if the class is of a certain type you could use the method .OfType
Use this method to get the value. Notice that you must make an instance in your case because the value change in your constructor.
public static object GetPropValue(Type src, string propName)
{
var prop = src.GetProperty(propName);
var instance = Activator.CreateInstance(src);
var value = prop.GetValue(instance);
return value;
}
Instead of
UnitId = x.GetProperty("UnitId", BindingFlags.Public),
use
UnitId = GetPropValue(x,"UnitId"),

Categories