MP.TaskMan:

- Completato aggiunta progetto
- porting classi servizi/controlli/db a progetto
- compilazione OK
- manca gestione connString ottimizzata x il DB di origine CORRETTO (LAND/PROG/STATS)
This commit is contained in:
Samuele Locatelli
2024-10-26 11:02:26 +02:00
parent 880df2a180
commit ffec555e85
30 changed files with 797 additions and 421 deletions
+438
View File
@@ -0,0 +1,438 @@
using Microsoft.EntityFrameworkCore;
using Microsoft.Extensions.Configuration;
using MP.TaskMan.Models;
using NLog;
using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Threading.Tasks;
using static MP.TaskMan.Objects.Enums;
namespace MP.TaskMan.Controllers
{
public class MpTaskController: IDisposable
{
public MpTaskController(IConfiguration configuration)
{
_configuration = configuration;
Log.Info("Avviato MpTaskController");
}
private static IConfiguration _configuration;
private static Logger Log = LogManager.GetCurrentClassLogger();
public DateTime CalcNextExe(TaskListModel taskRec)
{
DateTime dtNext = DateTime.Today;
try
{
// calcolo next exec da tipo...
switch (taskRec.Freq)
{
case TaskFreqType.ND:
dtNext = taskRec.DtLastExec.AddDays(taskRec.Cad);
break;
case TaskFreqType.Sec:
dtNext = taskRec.DtLastExec.AddSeconds(taskRec.Cad);
break;
case TaskFreqType.Min:
dtNext = taskRec.DtLastExec.AddMinutes(taskRec.Cad);
break;
case TaskFreqType.Hour:
dtNext = taskRec.DtLastExec.AddHours(taskRec.Cad);
break;
case TaskFreqType.Day:
dtNext = taskRec.DtLastExec.AddDays(taskRec.Cad);
break;
case TaskFreqType.Week:
dtNext = taskRec.DtLastExec.AddDays(7 * taskRec.Cad);
break;
case TaskFreqType.Month:
dtNext = taskRec.DtLastExec.AddMonths(taskRec.Cad);
break;
case TaskFreqType.Year:
dtNext = taskRec.DtLastExec.AddYears(taskRec.Cad);
break;
default:
dtNext = taskRec.DtLastExec.AddDays(taskRec.Cad);
break;
}
}
catch (Exception exc)
{
Log.Error($"Eccezione in CalcNextExe{Environment.NewLine}{exc}");
}
return dtNext;
}
/// <summary>
/// Elenco da tabella Config
/// </summary>
/// <returns></returns>
public List<ConfigModel> ConfigGetAll()
{
List<ConfigModel> dbResult = new List<ConfigModel>();
using (var dbCtx = new TaskContext(_configuration))
{
dbResult = dbCtx
.DbSetConfig
.AsNoTracking()
.OrderBy(x => x.Chiave)
.ToList();
}
return dbResult;
}
public void Dispose()
{
_configuration = null;
}
/// <summary>
/// Chiamata esecuzione di un singolo task programmato
/// </summary>
/// <param name="TaskId"></param>
/// <param name="SchedNext">Se true rischedula successiva chiamata</param>
/// <returns></returns>
public TaskResultModel ExecuteSqlTask(int TaskId, bool SchedNext)
{
TaskResultModel callRes = new TaskResultModel();
using (var dbCtx = new TaskContext(_configuration))
{
// imposto timeout a 5 min
//var currTimeout = dbCtx.Database.GetCommandTimeout();
dbCtx.Database.SetCommandTimeout(TimeSpan.FromMinutes(5));
try
{
DateTime dtStart = DateTime.Now;
// recupero i dati da richiamare...
var currRec = dbCtx
.DbSetTaskList
.Where(x => x.TaskId == TaskId)
.FirstOrDefault();
if (currRec != null)
{
// recupero comando
string sqlCommand = currRec.Command;
string rawParams = currRec.Args;
callRes = dbCtx
.DbSetTaskResult
.FromSqlRaw($"EXEC {sqlCommand} {rawParams}")
.AsNoTracking()
.AsEnumerable()
.FirstOrDefault();
DateTime dtEnd = DateTime.Now;
// preparo record esecuzione...
TaskExecModel resRec = new TaskExecModel()
{
TaskId = TaskId,
DtStart = dtStart,
DtEnd = dtEnd,
IsError = callRes.ExecResult < 0,
Result = callRes.TextResult
};
dbCtx
.DbSetTaskExe
.Add(resRec);
// aggiorno record chiamata...
currRec.DtLastExec = dtStart;
currRec.LastResult = resRec.Result;
currRec.LastIsError = resRec.IsError;
currRec.LastDuration = dtEnd.Subtract(dtStart).TotalSeconds;
// solo se richiesto rischedulazione ricalcola chiamata
if (SchedNext)
{
// calcolo prossima esecuzione...
currRec.DtNextExec = CalcNextExe(currRec);
}
// segno modificato
dbCtx.Entry(currRec).State = EntityState.Modified;
// salvo modifiche!
dbCtx.SaveChanges();
}
}
catch (Exception exc)
{
Log.Error($"Eccezione in ExecuteSqlCommand{Environment.NewLine}{exc}");
}
}
return callRes;
}
/// <summary>
/// Annulla modifiche su una specifica entity (cancel update)
/// </summary>
/// <param name="item"></param>
/// <returns></returns>
public bool RollBackEntity(object item)
{
bool answ = false;
using (var dbCtx = new TaskContext(_configuration))
{
try
{
if (dbCtx.Entry(item).State == Microsoft.EntityFrameworkCore.EntityState.Deleted || dbCtx.Entry(item).State == Microsoft.EntityFrameworkCore.EntityState.Modified)
{
dbCtx.Entry(item).Reload();
}
}
catch (Exception exc)
{
Log.Error($"Eccezione in rollBackEntity{Environment.NewLine}{exc}");
}
}
return answ;
}
/// <summary>
/// Ricerca task dato tipo + num max (desc)
/// </summary>
/// <param name="TaskId">TaskId da cui deriva</param>
/// <returns></returns>
public List<TaskExecModel> TaskExecGetFilt(int TaskId, int maxRec)
{
List<TaskExecModel> dbResult = new List<TaskExecModel>();
using (var dbCtx = new TaskContext(_configuration))
{
dbResult = dbCtx
.DbSetTaskExe
.Include(x => x.TaskListNav)
.Where(x => (x.TaskId == TaskId))
.OrderByDescending(x => x.DtStart)
.Take(maxRec)
.ToList();
}
return dbResult;
}
/// <summary>
/// Upsert record TaskExec
/// </summary>
/// <param name="rec2upd">Record da aggiornare/inserire</param>
/// <returns></returns>
public bool TaskExecUpsert(TaskExecModel rec2upd)
{
bool done = false;
using (var dbCtx = new TaskContext(_configuration))
{
try
{
var currData = dbCtx
.DbSetTaskExe
.Where(x => x.TaskExecId == rec2upd.TaskExecId)
.FirstOrDefault();
if (currData != null)
{
currData.TaskId = rec2upd.TaskId;
currData.DtStart = rec2upd.DtStart;
currData.DtEnd = rec2upd.DtEnd;
currData.IsError = rec2upd.IsError;
currData.Result = rec2upd.Result;
dbCtx.Entry(currData).State = EntityState.Modified;
}
else
{
dbCtx
.DbSetTaskExe
.Add(rec2upd);
}
dbCtx.SaveChanges();
done = true;
}
catch (Exception exc)
{
Log.Error($"Eccezione in TaskExecUpsert{Environment.NewLine}{exc}");
}
}
return done;
}
/// <summary>
/// Ricerca task dato tipo e
/// </summary>
/// <param name="TType"></param>
/// <returns></returns>
public List<TaskListModel> TaskListGetAll(Task2ExeType TType)
{
List<TaskListModel> dbResult = new List<TaskListModel>();
using (var dbCtx = new TaskContext(_configuration))
{
dbResult = dbCtx
.DbSetTaskList
.Where(x => (TType == Task2ExeType.ND || x.TType == TType))
.OrderBy(x => x.Ordinal)
.ToList();
}
return dbResult;
}
/// <summary>
/// Esegue registrazione di un Task generico (NON SQL)
/// </summary>
/// <param name="TaskId"></param>
/// <param name="SchedNext">Se true rischedula successiva chiamata</param>
/// <param name="ResRec">Record esecuzione task esterno</param>
/// <returns></returns>
public TaskResultModel TaskExecSaveExecuted(int TaskId, bool SchedNext, TaskExecModel ResRec)
{
TaskResultModel callRes = new TaskResultModel();
using (var dbCtx = new TaskContext(_configuration))
{
try
{
// recupero i dati da richiamare...
var currRec = dbCtx
.DbSetTaskList
.Where(x => x.TaskId == TaskId)
.FirstOrDefault();
if (currRec != null)
{
// registro task ricevuto
dbCtx
.DbSetTaskExe
.Add(ResRec);
// aggiorno record chiamata...
currRec.DtLastExec = ResRec.DtStart;
currRec.LastResult = ResRec.Result;
currRec.LastIsError = ResRec.IsError;
currRec.LastDuration = ResRec.DtEnd.Subtract(ResRec.DtStart).TotalSeconds;
// solo se richiesto rischedulazione ricalcola chiamata
if (SchedNext)
{
// calcolo prossima esecuzione...
currRec.DtNextExec = CalcNextExe(currRec);
}
// segno modificato
dbCtx.Entry(currRec).State = EntityState.Modified;
// salvo modifiche!
dbCtx.SaveChanges();
}
}
catch (Exception exc)
{
Log.Error($"Eccezione in TaskExecSaveExecuted{Environment.NewLine}{exc}");
}
}
return callRes;
}
/// <summary>
/// Update ordinamento task
/// </summary>
/// <param name="rec2upd">Record da spostare x priorità</param>
/// <returns></returns>
public bool TaskListMove(TaskListModel rec2upd, bool moveUp)
{
bool done = false;
using (var dbCtx = new TaskContext(_configuration))
{
try
{
var currData = dbCtx
.DbSetTaskList
.Where(x => x.TaskId == rec2upd.TaskId)
.FirstOrDefault();
if (currData != null)
{
int actOrdinal = currData.Ordinal;
TaskListModel? otherRec = null;
// cerco, secondo richiesta, precedente o successivo
if (moveUp)
{
otherRec = dbCtx
.DbSetTaskList
.Where(x => x.Ordinal < currData.Ordinal)
.OrderByDescending(x => x.Ordinal)
.FirstOrDefault();
}
else
{
otherRec = dbCtx
.DbSetTaskList
.Where(x => x.Ordinal > currData.Ordinal)
.OrderBy(x => x.Ordinal)
.FirstOrDefault();
}
// inverto ordinale SE ho record
if (otherRec != null)
{
currData.Ordinal = otherRec.Ordinal;
otherRec.Ordinal = actOrdinal;
dbCtx.Entry(currData).State = EntityState.Modified;
dbCtx.Entry(otherRec).State = EntityState.Modified;
}
}
//salvo
dbCtx.SaveChanges();
done = true;
}
catch (Exception exc)
{
Log.Error($"Eccezione in TaskListUpsert{Environment.NewLine}{exc}");
}
}
return done;
}
/// <summary>
/// Upsert record TaskList
/// </summary>
/// <param name="rec2upd">Record da aggiornare/inserire</param>
/// <returns></returns>
public bool TaskListUpsert(TaskListModel rec2upd)
{
bool done = false;
using (var dbCtx = new TaskContext(_configuration))
{
try
{
var currData = dbCtx
.DbSetTaskList
.Where(x => x.TaskId == rec2upd.TaskId && rec2upd.TaskId > 0)
.FirstOrDefault();
if (currData != null)
{
currData.Ordinal = rec2upd.Ordinal;
currData.Name = rec2upd.Name;
currData.Descript = rec2upd.Descript;
currData.Command = rec2upd.Command;
currData.Args = rec2upd.Args;
currData.Freq = rec2upd.Freq;
currData.Cad = rec2upd.Cad;
currData.DtLastExec = rec2upd.DtLastExec;
currData.DtNextExec = rec2upd.DtNextExec;
currData.LastDuration = rec2upd.LastDuration;
currData.LastResult = rec2upd.LastResult;
dbCtx.Entry(currData).State = EntityState.Modified;
}
else
{
dbCtx
.DbSetTaskList
.Add(rec2upd);
}
dbCtx.SaveChanges();
done = true;
}
catch (Exception exc)
{
Log.Error($"Eccezione in TaskListUpsert{Environment.NewLine}{exc}");
}
}
return done;
}
}
}
+11 -9
View File
@@ -6,14 +6,6 @@
<ImplicitUsings>enable</ImplicitUsings>
</PropertyGroup>
<ItemGroup>
<Compile Remove="TaskExeList.razor.cs" />
</ItemGroup>
<ItemGroup>
<Content Remove="TaskExeList.razor" />
</ItemGroup>
<ItemGroup>
<SupportedPlatform Include="browser" />
@@ -21,6 +13,13 @@
<ItemGroup>
<PackageReference Include="Microsoft.AspNetCore.Components.Web" Version="6.0.33" />
<PackageReference Include="Microsoft.EntityFrameworkCore" Version="6.0.33" />
<PackageReference Include="Microsoft.EntityFrameworkCore.Design" Version="6.0.9">
<PrivateAssets>all</PrivateAssets>
<IncludeAssets>runtime; build; native; contentfiles; analyzers; buildtransitive</IncludeAssets>
</PackageReference>
<PackageReference Include="Microsoft.EntityFrameworkCore.Relational" Version="6.0.33" />
<PackageReference Include="Microsoft.EntityFrameworkCore.SqlServer" Version="6.0.33" />
<PackageReference Include="Microsoft.Extensions.Configuration.Binder" Version="6.0.0" />
<PackageReference Include="Newtonsoft.Json" Version="13.0.3" />
<PackageReference Include="NLog" Version="5.3.4" />
@@ -30,7 +29,10 @@
<ItemGroup>
<Folder Include="Migrations\" />
<Folder Include="Controllers\" />
</ItemGroup>
<ItemGroup>
<ProjectReference Include="..\Egw.Core\Egw.Core.csproj" />
</ItemGroup>
</Project>
+22
View File
@@ -0,0 +1,22 @@
using System;
using System.Collections.Generic;
using System.ComponentModel.DataAnnotations.Schema;
#nullable disable
// <Auto-Generated>
// This is here so CodeMaid doesn't reorganize this document
// </Auto-Generated>
namespace MP.TaskMan.Models
{
[Table("Config")]
public partial class ConfigModel
{
public string Chiave { get; set; }
public string Valore { get; set; }
/// <summary>
/// Valore di default/riferimento per la variabile
/// </summary>
public string ValoreStd { get; set; }
public string Note { get; set; }
}
}
+26
View File
@@ -0,0 +1,26 @@
using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Threading.Tasks;
namespace MP.TaskMan.Services
{
public class ReloadEventArgs : EventArgs
{
#region Public Constructors
public ReloadEventArgs(string messaggio)
{
this.ReloadMessage = messaggio;
}
#endregion Public Constructors
#region Public Properties
public string ReloadMessage { get; set; } = "";
#endregion Public Properties
}
}
+111
View File
@@ -0,0 +1,111 @@
using Microsoft.Extensions.Configuration;
using Newtonsoft.Json;
using NLog;
using RestSharp;
using System;
using System.Collections.Generic;
using System.IO;
using System.Linq;
using System.Text;
using System.Threading.Tasks;
using System.Web;
using static System.Net.Mime.MediaTypeNames;
namespace MP.TaskMan.Services
{
public class RestCallService
{
#region Public Constructors
/// <summary>
/// Init classe
/// </summary>
/// <param name="configuration"></param>
public RestCallService(IConfiguration configuration)
{
_configuration = configuration;
// verifico la url base
apiUrl = _configuration.GetValue<string>("ServerConf:Prog.ApiUrl");
// fix opzioni base del RestClient
restOptStd = new RestClientOptions
{
Timeout = TimeSpan.FromSeconds(60),
BaseUrl = new Uri(apiUrl)
};
}
private string apiUrl = "";
#endregion Public Constructors
#region Public Methods
/// <summary>
/// Stato server da chiamare x verifiche preliminari stato API REST
/// </summary>
/// <param name="ApiUrl"></param>
/// <returns></returns>
public async Task<string> CheckServer()
{
string answ = "ND";
// cerco online
using (RestClient client = new RestClient(restOptStd))
{
var request = new RestRequest($"api/health", Method.Get);
var response = await client.GetAsync(request);
// controllo risposta
if (response.StatusCode == System.Net.HttpStatusCode.OK)
{
// verifico risposta
if (response.Content != null)
{
answ = response.Content.Replace("\"", "");
}
}
}
return answ;
}
/// <summary>
/// Effettua una generica chiamata ApiRest
/// </summary>
/// <param name="ApiUrl">URL Api di base</param>
/// <param name="ApiRequest">Richiesta completa</param>
/// <returns></returns>
public async Task<RestResponse> CallRestGet(string ApiUrl, string ApiRequest)
{
RestResponse response;
// cerco online
using (RestClient client = new RestClient(ApiUrl))
{
var request = new RestRequest(ApiRequest, Method.Get);
response = await client.GetAsync(request);
}
return response;
}
#endregion Public Methods
#region Private Fields
private static IConfiguration? _configuration;
/// <summary>
/// Classe logger
/// </summary>
private static Logger Log = LogManager.GetCurrentClassLogger();
#endregion Private Fields
#region Private Properties
/// <summary>
/// Conf client RestSharp standard:
/// - base URI al sito
/// - timeout 1 min
/// </summary>
private RestClientOptions restOptStd { get; set; } = new RestClientOptions();
#endregion Private Properties
}
}
+4 -5
View File
@@ -1,8 +1,7 @@
using Microsoft.Extensions.Configuration;
using Microsoft.Extensions.Options;
using MP.AppAuth.Services;
using MP.TaskMan.Controllers;
using MP.TaskMan.DatabaseModels;
using MP.TaskMan.Models;
using Newtonsoft.Json;
using NLog;
using StackExchange.Redis;
@@ -41,8 +40,8 @@ namespace MP.TaskMan.Services
else
{
StringBuilder sb = new StringBuilder();
MLController = new MpLandController(configuration);
sb.AppendLine($"TaskService | MpLandController OK");
MLController = new MpTaskController(configuration);
sb.AppendLine($"TaskService | MpTaskController OK");
Log.Info(sb.ToString());
// sistemo i parametri x redHas...
CodModulo = _configuration.GetValue<string>("ServerConf:CodModulo");
@@ -330,7 +329,7 @@ namespace MP.TaskMan.Services
#region Private Properties
private static MpLandController MLController { get; set; } = null!;
private static MpTaskController MLController { get; set; } = null!;
private RestCallService RCallService { get; set; } = null!;
#endregion Private Properties
+116
View File
@@ -0,0 +1,116 @@
using System;
using Microsoft.EntityFrameworkCore;
using Microsoft.EntityFrameworkCore.Metadata;
using Microsoft.Extensions.Configuration;
using MP.TaskMan.Models;
using NLog;
#nullable disable
// <Auto-Generated>
// This is here so CodeMaid doesn't reorganize this document
// </Auto-Generated>
namespace MP.TaskMan
{
public partial class TaskContext : DbContext
{
#region Private Fields
private static NLog.Logger Log = LogManager.GetCurrentClassLogger();
private IConfiguration _configuration;
#endregion Private Fields
#region Public Constructors
public TaskContext(IConfiguration configuration)
{
_configuration = configuration;
}
public TaskContext(DbContextOptions<TaskContext> options) : base(options)
{
}
#endregion Public Constructors
#region Public Properties
public virtual DbSet<ConfigModel> DbSetConfig { get; set; }
public virtual DbSet<TaskListModel> DbSetTaskList { get; set; }
public virtual DbSet<TaskExecModel> DbSetTaskExe { get; set; }
public virtual DbSet<TaskResultModel> DbSetTaskResult { get; set; }
#endregion Public Properties
#region Private Methods
partial void OnModelCreatingPartial(ModelBuilder modelBuilder);
#endregion Private Methods
#region Protected Methods
protected override void OnConfiguring(DbContextOptionsBuilder optionsBuilder)
{
if (!optionsBuilder.IsConfigured)
{
string connString = _configuration.GetConnectionString("MP.TaskMan");
if (string.IsNullOrEmpty(connString))
{
connString = _configuration.GetConnectionString("MP.All");
}
if (string.IsNullOrEmpty(connString))
{
connString = _configuration.GetConnectionString("MP.Data");
}
if (string.IsNullOrEmpty(connString))
{
connString = _configuration.GetConnectionString("MP.Mon");
}
if (string.IsNullOrEmpty(connString))
{
connString = _configuration.GetConnectionString("MP.STATS");
}
if (string.IsNullOrEmpty(connString))
{
connString = _configuration.GetConnectionString("MP.Land");
}
optionsBuilder.UseSqlServer(connString);
//optionsBuilder.UseSqlServer("Server=SQL2016DEV;Database=MoonPro;Trusted_Connection=True;");
}
}
protected override void OnModelCreating(ModelBuilder modelBuilder)
{
modelBuilder.HasAnnotation("Relational:Collation", "SQL_Latin1_General_CP1_CI_AS");
modelBuilder.Entity<ConfigModel>(entity =>
{
entity.HasKey(e => e.Chiave);
entity.ToTable("Config");
entity.Property(e => e.Chiave)
.HasMaxLength(50)
.HasColumnName("chiave");
entity.Property(e => e.Note).HasColumnName("note");
entity.Property(e => e.Valore).HasColumnName("valore");
entity.Property(e => e.ValoreStd)
.HasColumnName("valoreStd")
.HasComment("Valore di default/riferimento per la variabile");
});
OnModelCreatingPartial(modelBuilder);
}
#endregion Protected Methods
}
}
+2 -1
View File
@@ -1,5 +1,6 @@
using Microsoft.AspNetCore.Components;
using MP.TaskMan.Models;
using MP.TaskMan.Services;
namespace MP.TaskMan
{
@@ -18,7 +19,7 @@ namespace MP.TaskMan
#region Protected Properties
[Inject]
protected TaskService TService { get; set; }
protected TaskService TService { get; set; } = null!;
#endregion Protected Properties
+4 -4
View File
@@ -1,6 +1,6 @@
using MailKit;
using Microsoft.AspNetCore.Components;
using MongoDB.Bson;
using MP.TaskMan.Models;
using MP.TaskMan.Services;
using Newtonsoft.Json;
using Newtonsoft.Json.Linq;
using NLog;
@@ -29,7 +29,7 @@ namespace MP.TaskMan
#region Protected Properties
[Inject]
protected NavigationManager NavManager { get; set; }
protected NavigationManager NavManager { get; set; } = null!;
/// <summary>
/// Show error mode: 0 = tutti 1 = solo errori 2 = solo ok
@@ -51,7 +51,7 @@ namespace MP.TaskMan
protected int totalCount { get; set; } = 0;
[Inject]
protected TaskService TService { get; set; }
protected TaskService TService { get; set; } = null!;
#endregion Protected Properties