Always create a new ILogger or store them? - asp.net-core

I have written a middleware for my web application.
The middleware handles special requests and writes multiple information from multiple sources to the log. For example like the following code snippet:
public class MyMiddleware
{
public async Task Invoke(HttpContext context)
{
var dataToBeLogged = await context.Request.ReadFromJsonAsync<LogEntry[]>();
foreach(var l in dataToBeLogged)
{
var loggerName = string.IsNullOrEmpty(l.LoggerName) ? "Default" : l.LoggerName;
var logger = _loggerFactory.CreateLogger($"{env.ApplicationName}.Client.{loggerName}");
logger.Log(l.Level, l.Exception, l.Message);
}
}
}
The loggerName and therefor the logger might be differ but it could also be that a logger with the same loggerName hast been created before.
My question is what is the best practice of handling the logger creation?
Should I always create a new logger? or
Should I create a Dictionary where I store loggers which has been created before in the Invoke method (Example 1)? or
Because the instance of the middleware doesn't change at runtime, should I create the dictionary a class level (Example 2)?
Example 1
public class MyMiddleware
{
public async Task Invoke(HttpContext context)
{
var dataToBeLogged = await context.Request.ReadFromJsonAsync<LogEntry[]>();
var dict = new Dictionary<string, ILogger>();
foreach(var l in dataToBeLogged)
{
var loggerName = string.IsNullOrEmpty(l.LoggerName) ? "Default" : l.LoggerName;
if (!dict.ContainsKey(loggerName))
dict.Add(loggerName, _loggerFactory.CreateLogger($"{env.ApplicationName}.Client.{loggerName}"));
var logger = dict[loggerName];
logger.Log(l.Level, l.Exception, l.Message);
}
}
}
Example 2
public class MyMiddleware
{
private readonly dict = new Dictionary<string, ILogger>();
public async Task Invoke(HttpContext context)
{
var dataToBeLogged = await context.Request.ReadFromJsonAsync<LogEntry[]>();
foreach(var l in dataToBeLogged)
{
var loggerName = string.IsNullOrEmpty(l.LoggerName) ? "Default" : l.LoggerName;
if (!dict.ContainsKey(loggerName))
dict.Add(loggerName, _loggerFactory.CreateLogger($"{env.ApplicationName}.Client.{loggerName}"));
var logger = dict[loggerName];
logger.Log(l.Level, l.Exception, l.Message);
}
}
}
LogEntry class
public class LogEntry
{
public string LoggerName { get;set; }
public int Level { get;set; }
public Exception Exception { get;set; }
public string Message { get;set; }
}
Example data
[
{ loggerName: "LoggerOne", level: 2, exception: null, message: "This is an information" },
{ loggerName: "LoggerTwo", level: 3, exception: null, message: "This is a warning" },
{ loggerName: "LoggerOne", level: 4, exception: { message: "A Exception message", stackTrace: "..." }, message: "This is an error" }
]
Expected log output
MyProject.Client.LoggerOne: Information: This is an information
MyProject.Client.LoggerTwo: Warning: This is a warning
MyProject.Client.LoggerOne: Error: This is an error
MyProject.Client.LoggerOne: Error: A Exception message
at ...
at ...
at ...

Related

Passing arguments to IAsyncAuthorizationFilter inside TypeFilterAttribute

I'm trying to pass arguments to an IAsyncAuthorizationFilter filter from the TypeFilterAttribute. I made a simple example which according to answers such as this one https://stackoverflow.com/a/44435070/937131 should work.
public class SimpleAuthorizeServiceAttribute : TypeFilterAttribute
{
public SimpleAuthorizeServiceAttribute(string service, string context, AccessType access) : base(typeof(SimpleAuthorizeServiceAttribute))
{
Arguments = new[] { new PermissionData(service, context, access) };
}
public class SimpleAuthorizeServiceFilter : IAsyncAuthorizationFilter
{
private readonly PermissionData _permissionData;
public SimpleAuthorizeServiceFilter(PermissionData permissionData)
{
_permissionData = permissionData;
}
public virtual Task OnAuthorizationAsync(AuthorizationFilterContext context)
{
var test = _permissionData.Service != null;
return Task.CompletedTask;
}
}
}
I've then applied this to a method like this:
[HttpGet]
[SimpleAuthorizeService("test-service", "test-context", AccessType.Read)]
public async Task<bool> ServiceTest()
{
bool isAllowed = this.HttpContext.IsUserAllowedToAccess(10, 20, 30);
return isAllowed;
}
Which fails with
System.InvalidOperationException: A suitable constructor for type
'SecurityAttribute.Application.ServiceToServiceAttribute.SimpleAuthorizeServiceAttribute'
could not be located. Ensure the type is concrete and all parameters
of a public constructor are either registered as services or passed as
arguments. Also ensure no extraneous arguments are provided.
I thought this might be because I'm not using params for the TypeFilterAttribute (which is dumb). So i rewrote it to this monstrosity..
public class SimpleAuthorizeServiceAttribute : TypeFilterAttribute
{
public SimpleAuthorizeServiceAttribute(params string[] data) : base(typeof(SimpleAuthorizeServiceAttribute))
{
// hoorray for non type safe paramaters...
var success = AccessType.TryParse(data[2], out AccessType access);
Arguments = new[] { new PermissionData(data[0], data[1], access) };
}
public class SimpleAuthorizeServiceFilter : IAsyncAuthorizationFilter
{
private readonly PermissionData _permissionData;
public SimpleAuthorizeServiceFilter(PermissionData permissionData)
{
_permissionData = permissionData;
}
public virtual Task OnAuthorizationAsync(AuthorizationFilterContext context)
{
var test = _permissionData.Service != null;
return Task.CompletedTask;
}
}
}
....
[HttpGet]
[SimpleAuthorizeService("test-service", "test-context", "Read")]
public async Task<bool> ServiceTest()
{
bool isAllowed = this.HttpContext.IsUserAllowedToAccess(10, 20, 30);
return isAllowed;
}
But that still trows an error:
System.InvalidOperationException: A suitable constructor for type
'SecurityAttribute.Application.ServiceToServiceAttribute.SimpleAuthorizeServiceAttribute'
could not be located. Ensure the type is concrete and all parameters
of a public constructor are either registered as services or passed as
arguments. Also ensure no extraneous arguments are provided. at
Microsoft.Extensions.DependencyInjection.ActivatorUtilities.FindApplicableConstructor(Type
instanceType, Type[] argumentTypes, ConstructorInfo&
matchingConstructor, Nullable`1[]& matchingParameterMap)
So how is one supposed to pass arguments to a IAsyncAuthorizationFilter?
The issue is with this line
public SimpleAuthorizeServiceAttribute(string service, string context, AccessType access) :
base(typeof(SimpleAuthorizeServiceAttribute)) // problem is here
{
Arguments = new[] { new PermissionData(service, context, access) };
}
it should be SimpleAuthorizeServiceFilter not SimpleAuthorizeServiceAttribute.
replace it and it should work.

How to get response via Masstransit?

I want to get a response (GetStatusResponse) from consumer (GetStatusConsumer).
My request is putted in Rabbit queue "getstatus" but my consumer is not rise and timeout occurs.
Publish-method and Consumer nested in one project
It seems to me trouble in the Startup.cs. Could you help me?
I have following code in Startup.cs
...
services.AddSingleton(provider =>
{
var getStatusBusOptions = provider.GetRequiredService<IOptions<BusOptions>>().Value;
var bus = Bus.Factory.CreateUsingRabbitMq(sbc =>
{
var host = sbc.Host(new Uri(getStatusBusOptions.HostUri), h =>
{
h.Username(getStatusBusOptions.UserName);
h.Password(getStatusBusOptions.Password);
});
sbc.ReceiveEndpoint("getstatus", ep =>
{
ep.Consumer<GetStatusConsumer>(provider);
ep.PrefetchCount = getStatusBusOptions.PrefetchCount;
ep.UseConcurrencyLimit(getStatusBusOptions.UseConcurrencyLimit);
});
});
return new GetStatusBus
{
Bus = bus,
HostUri = getStatusBusOptions.HostUri
};
});
...
Following code in class GetStatusPublisher.cs
public class GetStatusPublisher : IGetStatusPublisher
{
readonly GetStatusBus _bus;
public GetStatusPublisher(GetStatusBus bus)
{
_bus = bus;
}
public async Task<Tout> GetResponse<Tin, Tout>(Tin request) where Tin : class where Tout : class
{
var serviceAddress = new Uri($"rabbitmq://rabbitmq.test.com/jgt/getstatus");
var timeout = TimeSpan.FromSeconds(30);
var client = new MessageRequestClient<Tin, Tout>(_bus.Bus, serviceAddress, timeout);
var resp = await client.Request(request); // <== Timeout here and don't rise consumer (GetStatusConsumer)
return resp;
}
Here is Publish-method:
...
readonly IGetStatusPublisher _getStatusPublisher;
...
var resp = await _getStatusPublisher.GetResponse<GetStatusRequest, GetStatusResponse>(statusReq);
Consumer has following code:
public class GetStatusConsumer : MetricWriter, IConsumer<GetStatusRequest>
{
public GetStatusConsumer(IMetrics metrics) : base(metrics)
{
......
}
public async Task Consume(ConsumeContext<GetStatusRequest> context)
{
....
}
}
First things first, I don't think you're starting the bus. That's the major issue.
However...
Are you using an antique version of MassTransit? Your code is seriously out of date, and I'd suggest updating it to use the current version/syntax as your code example above has so many things wrong with it.
services.AddMassTransit(x =>
{
x.AddConsumer<GetStatusConsumer>();
x.UsingRabbitMq((context, cfg) =>
{
var getStatusBusOptions = context.GetRequiredService<IOptions<BusOptions>>().Value;
cfg.Host(new Uri(getStatusBusOptions.HostUri), h =>
{
h.Username(getStatusBusOptions.UserName);
h.Password(getStatusBusOptions.Password);
});
cfg.PrefetchCount = getStatusBusOptions.PrefetchCount;
cfg.ConcurrentMessageLimit = getStatusBusOptions.UseConcurrencyLimit;
cfg.ConfigureEndpoints(context);
});
});
services.AddMassTransitHostedService();
services.AddGenericRequestClient();
Then, you can simply add a dependency on IRequestClient<T> to send requests and get responses. Your updated publisher code may look like:
public class GetStatusPublisher :
IGetStatusPublisher
{
public GetStatusPublisher(IServiceProvider provider)
{
_provider = provider;
}
public async Task<Tout> GetResponse<Tin, Tout>(Tin request) where Tin : class where Tout : class
{
var client = _provider.GetRequiredService<IRequestClient<Tin>>();
var response = await client.GetResponse<Tout>(request);
return response.Message;
}
}
If I use following code I get error: "System.InvalidOperationException: Cannot resolve scoped service 'MassTransit.IRequestClient`1[GetStatusTry.Contracts.GetStatusRequest]' from root provider....."
public async Task<Tout> GetResponse<Tin, Tout>(Tin request) where Tin : class where Tout : class
{
try
{
var client = _provider.GetRequiredService<IRequestClient<Tin>>(); // << --- here is error
var response = await client.GetResponse<Tout>(request);
return response.Message;
}
catch (Exception ex)
{
Console.WriteLine(ex.Message);
throw;
}
}
But this code works Ok:
public class Dev2Controller : ControllerBase
{
IRequestClient<GetStatusRequest> _client;
public Dev2Controller(IGetStatusPublisher getStatusPublisher, IRequestClient<GetStatusRequest> client)
{
_client = client;
}
[HttpPost]
public async Task<GetStatusResponse> GetStatus2()
{
var req = new GetStatusRequest { Statuses = new List<int> { 1, 2, 3 }, TerminalDescr = "try to get status2" };
var response = await _client.GetResponse<GetStatusResponse>(req);
return (response.Message);
}
}
this code is working
public class GetStatusPublisher : IGetStatusPublisher
{
readonly IServiceProvider _provider;
public GetStatusPublisher(IServiceProvider provider)
{
_provider = provider;
}
public async Task<Tout> GetResponse<Tin, Tout>(Tin request) where Tin : class where Tout : class
{
try
{
using (var _scope = _provider.CreateScope())
{
var client = _scope.ServiceProvider.GetRequiredService<IRequestClient<Tin>>();
var response = await client.GetResponse<Tout>(request);
return response.Message;
}
}
catch (Exception ex)
{
Console.WriteLine(ex.Message);
throw;
}
}
}

How do you manage the visible input fields accepted in an API HttpPost request?

In my API I have a Create method in my controller that accepts all of the models fields, but in the method I'm excluding the ID field since on a create it's generated. But in Swagger it's showing the following.
Is there a way for it not to show the following part?
"id": 0
Is a viewmodel how I should go about this?
I tried the following, but can't get it to work.
public class PartVM
{
public string Name { get; set; }
}
public interface IPartService
{
Task<Part> CreatePart(PartVM part);
Task<IEnumerable<Part>> GetParts();
Task<Part> GetPart(int partId);
}
public class PartService : IPartService
{
private readonly AppDbContext _appDbContext;
public PartService(AppDbContext appDbContext)
{
_appDbContext = appDbContext;
}
public async Task<Part> CreatePart(PartVM part)
{
var _part = new Part()
{
Name = part.Name
};
var result = await _appDbContext.Parts.AddAsync(_part);
await _appDbContext.SaveChangesAsync();
return result.Entity;
}
}
Here's my controller.
[Route("api/[controller]")]
[ApiController]
public class PartsController : ControllerBase
{
private readonly IPartService _partService;
public PartsController(IPartService partService)
{
_partService = partService;
}
[HttpPost]
public async Task<ActionResult<Part>> CreatePart(PartVM part)
{
try
{
if (part == null)
return BadRequest();
var _part = new Part()
{
Name = part.Name
};
var createdPart = await _partService.CreatePart(_part);
return CreatedAtAction(nameof(GetPart),
new { id = createdPart.Id}, createdPart);
}
catch (Exception /*ex*/)
{
return StatusCode(StatusCodes.Status500InternalServerError, "Error creating new record in the database");
}
}
I'm getting a build error saying "CS1503 Argument 1: cannot convert from 'MusicManager.Shared.Part' to 'MusicManager.Server.Data.ViewModels.PartVM'".
It's refering to "_part" in this line "var createdPart = await _partService.CreatePart(_part);".
Any help is appreciated, thank you!
you have a CreatePart method which receives a PartVM model, but you are sending a Part Model to it
change your method to this :
public async Task<Part> CreatePart(Part part)
{
var result = await _appDbContext.Parts.AddAsync(_part);
await _appDbContext.SaveChangesAsync();
return result.Entity;
}

Dependency Injection Quartz.Net Scheduler

I'm currently trying to use a repository to update some data in the DB using quartz.net.
Keep in mind that I'm using ASP.Net Core 3.1
The problem that I'm currently having is that when I'm injecting my IUserProjectRepository in the constructor of the IJob the job wont get executed and I also get an error in the Quartz DB implementation:
So, this is how my startup.cs looks like:
public void ConfigureServices(IServiceCollection services)
{
services.AddTransient<UserProjectStatusJob>();
services.AddTransient(provider => GetScheduler().Result);
}
....
private async Task<IScheduler> GetScheduler()
{
NameValueCollection properties = new NameValueCollection
{
{ "quartz.scheduler.instanceName", "Cliche" },
{ "quartz.scheduler.instanceId", "Cliche" },
{ "quartz.jobStore.type", "Quartz.Impl.AdoJobStore.JobStoreTX, Quartz" },
{ "quartz.jobStore.useProperties", "true" },
{ "quartz.jobStore.dataSource", "default" },
{ "quartz.jobStore.tablePrefix", "QRTZ_" },
{
"quartz.dataSource.default.connectionString",
"connectionstring"
},
{ "quartz.dataSource.default.provider", "SqlServer" },
{ "quartz.threadPool.threadCount", "1" },
{ "quartz.serializer.type", "json" },
};
var schedulerFactory = new StdSchedulerFactory(properties);
var scheduler = await schedulerFactory.GetScheduler();
await scheduler.Start();
return scheduler;
}
This is how my Job (UserProjectStatusJob) Looks like:
public class UserProjectStatusJob : IJob
{
private IUserProjectRepository _userProjectRepository;
public UserProjectStatusJob(IUserProjectRepository userProjectRepository)
{
this._userProjectRepository = userProjectRepository;
}
public Task Execute(IJobExecutionContext context)
{
try
{
JobDataMap dataMap = context.JobDetail.JobDataMap;
string userProjectId = dataMap.GetString("userProjectId");
string userProjectProjectId = dataMap.GetString("userProjectProjectId");
_userProjectRepository.CloseUserProject(userProjectProjectId, userProjectId);
}
catch (Exception ex)
{
Console.WriteLine(ex);
}
return Task.FromResult(0);
}
}
I create my job in the same UserProjectRepository:
public class UserProjectRepository : IUserProjectRepository
{
private readonly ApplicationDbContext _dbContext;
private readonly IFileService _fileService;
private readonly INotificationRepository _notificationRepository;
private readonly IScheduler _scheduler;
public UserProjectRepository(ApplicationDbContext dbContext,
IFileService fileService,
INotificationRepository notificationRepository,
IScheduler scheduler)
{
this._scheduler = scheduler;
this._notificationRepository = notificationRepository;
this._fileService = fileService;
this._dbContext = dbContext;
}
public async Task CreateCronJobForUserProject(UserProject userProject)
{
// Add Later in to startAt
TimeSpan timeToTrigger = userProject.Project.Assignment.DeadLine - DateTime.Now;
ITrigger trigger = TriggerBuilder.Create()
.WithIdentity($"Check Availability-{DateTime.Now}")
.StartAt(DateTime.Now.AddSeconds(15))
.WithPriority(1)
.Build();
IDictionary<string, object> map = new Dictionary<string, object>()
{
{"userProjectId", $"{userProject.Id}" },
{"userProjectProjectId", $"{userProject.ProjectId}" },
};
IJobDetail job = JobBuilder.Create<UserProjectStatusJob>()
.WithIdentity($"Check Availability-{DateTime.Now}")
.SetJobData(new JobDataMap(map))
.Build();
await this._scheduler.ScheduleJob(job, trigger);
}
}
EDIT:
Error:
After taking a closer look I did found this:
[14:46:50 ERR] An error occurred instantiating job to be executed. job= 'DEFAULT.Check Availability-10/28/2020 14:46:35'
Quartz.SchedulerException: Problem instantiating class 'IKL.Data.Services.UserProjectStatusJob: Cannot instantiate type which has no empty constructor (Parameter 'UserProjectStatusJob')'
---> System.ArgumentException: Cannot instantiate type which has no empty constructor (Parameter 'UserProjectStatusJob')

Why WF4 Constraints are not working with Activity and CodeActivity parent's types

I want to set constraint to activity to prevent adding it to some other activities.
I have problem with GetParentChain I think. I did everything like in msdn samples:
I have three activities: MyActivity, SqlNativeActivity and SqlActivity. This classes look like:
SqlNativeActivity:
public sealed class SqlNativeActivity : BaseNativeActivity
{
public Activity Activity { get; set; }
protected override void Execute(NativeActivityContext context)
{
}
}
public abstract class BaseNativeActivity : NativeActivity
{
protected ActivityConstraintsProvider ActivityConstraintsProvider;
protected abstract override void Execute(NativeActivityContext context);
}
SqlActivity:
public sealed class SqlActivity : BaseActivity
{
public Activity Activity { get; set; }
}
public abstract class BaseActivity : Activity
{
protected ActivityConstraintsProvider ActivityConstraintsProvider;
}
MyActivity:
public sealed class MyActivity : BaseActivity
{
public MyActivity()
{
ActivityConstraintsProvider = new ActivityConstraintsProvider();
ActivityConstraintsProvider.AddNotAcceptedParentActivity(typeof(SqlActivity));
ActivityConstraintsProvider.AddNotAcceptedParentActivity(typeof(SqlNativeActivity));
base.Constraints.Add(ActivityConstraintsProvider.CheckParent());
}
}
And I wrote ActivityConstraintsProvider in which I define List with not accepted parent types.
ActivityConstraintsProvider:
public class ActivityConstraintsProvider
{
private List<Type> _notAcceptedParentActivity;
public void AddNotAcceptedParentActivity(Type type)
{
if (_notAcceptedParentActivity == null)
_notAcceptedParentActivity = new List<Type>();
_notAcceptedParentActivity.Add(type);
}
public Constraint CheckParent()
{
var element = new DelegateInArgument<Activity>();
var context = new DelegateInArgument<ValidationContext>();
var result = new Variable<bool>();
var parent = new DelegateInArgument<Activity>();
var con = new Constraint<Activity>
{
Body = new ActivityAction<Activity, ValidationContext>
{
Argument1 = element,
Argument2 = context,
Handler = new Sequence
{
Variables =
{
result
},
Activities =
{
new ForEach<Activity>
{
Values = new GetParentChain
{
ValidationContext = context
},
Body = new ActivityAction<Activity>
{
Argument = parent,
Handler = new If()
{
Condition = new InArgument<bool>((env) => _notAcceptedParentActivity.Contains(parent.Get(env).GetType())),
Then = new Assign<bool>
{
Value = true,
To = result
},
}
}
},
new AssertValidation
{
Assertion = new InArgument<bool> { Expression = new Not<bool, bool> { Operand = result } },
Message = new InArgument<string> ("Decide can't be in Sql"),
}
}
}
}
};
return con;
}
}
And finally Main:
class Program
{
static void Main()
{
ValidationResults results;
Activity wf3 = new SqlActivity
{
Activity = new Sequence()
{
Activities =
{
new MyActivity
{
}
}
}
};
results = ActivityValidationServices.Validate(wf3);
Console.WriteLine("WF3 (SqlActivity):");
PrintResults(results);
//----------------------------------------------------------------
Activity wf4 = new SqlNativeActivity
{
Activity = new Sequence()
{
Activities =
{
new MyActivity
{
}
}
}
};
results = ActivityValidationServices.Validate(wf4);
Console.WriteLine("WF4 (SqlNativeActivity):");
PrintResults(results);
//----------------------------------------------------------------
}
static void PrintResults(ValidationResults results)
{
Console.WriteLine();
if (results.Errors.Count == 0 && results.Warnings.Count == 0)
{
Console.WriteLine(" No warnings or errors");
}
else
{
foreach (ValidationError error in results.Errors)
{
Console.WriteLine(" Error: " + error.Message);
}
foreach (ValidationError warning in results.Warnings)
{
Console.WriteLine(" Warning: " + warning.Message);
}
}
Console.WriteLine();
}
}
And the problem is that if my sql activity is inherites from System.Activities.NativeActivity (SqlNativeActivity) constraints are working very well, but if I define constraints and parent is activity inherites from System.Activities.Activity or System.Activities.CodeActivity constraints validation is not working at all.
Anybody can help me with my problem?
Thank you in advance :)
if you create a custom activity (inheriting from System.Activities.CodeActivity), your validation should be done at CacheMetaData:
protected override void CacheMetadata(CodeActivityMetadata metadata)
{
//Validate here
base.CacheMetadata(metadata);
}