How to handle application start event in ASP.NET module - c#

I am writing an asp.net HTTP module which needs to read configuration data once from a local file (say config.xml stored in application root directory) and then based on configuration perform some processing on incoming requests.
Since there is no Application_Start/Application_init hooking available in Asp.NET modules, what would be the best way to handle the scenario. I am trying to avoid reading configuration file each time a request comes. Ideally, I want to read the config file when application starts.
I need to code this in http module only and do not want to use Global.asax

I'd go for a simple property, something like this ...
public MyConfig Config
{
get
{
MyConfig _config = Application["MyConfig"] as MyConfig;
if (_config == null)
{
_config = new MyConfig(...);
Application["MyConfig"] = _config;
}
return _config;
}
}
that way you just access whatever you need from Config via the property ...
int someValue = Config.SomeValue;
and it's loaded into the application object if it hasn't been already
If you need the config on a per-user basis rather than globally, then just use Session["MyConfig"] instead of Application["MyConfig"]

Not sure if this would work, but you might be able to implement this in the module's init method.

In the init method of your httpmodule you can hook up to the event in the context.
For example :
public void Init(HttpApplication context)
{
context.PostRequestHandlerExecute += (sender, e) =>
{
Page p = context.Context.Handler as Page;
if (p != null)
{
///Code here
}
};
}

public SomeHttpModule : IHttpModule
{
private static readonly Configuration Configuration =
ConigurationReader.Read();
}

static variable did the trick. here is the code if someone is interested -
static string test;
public void Init(HttpApplication application)
{
application.BeginRequest +=(new EventHandler(this.Application_BeginRequest));
test = "hi";
application.EndRequest +=(new EventHandler(this.Application_EndRequest));
}
private void Application_BeginRequest(Object source,EventArgs e)
{
{
HttpApplication application = (HttpApplication)source ;
HttpContext context = application.Context;
context.Response.Write(test);
}
}

Related

How to use Javascript in .NET MAUI Blazor App Hybrid

when ever i try to put javascript link in .NET MAUI Blazor App Hybrid it says "Script tags should not be placed inside components because they cannot be updated dynamically."
Please help
i want to use javascript in .NET MUAI Blazor App Hybrid
You should try to use JavaScript using the modular approach.
Let's assume you have file hello.js which contain functions like below:
export function helloWorld (name) {
console.log("Hello "+ name);
}
Try creating an interop for your JS file like:
public class Interop : IDisposable
{
private readonly string _filePath;
protected readonly IJSRuntime _jsRuntime;
private Task<IJSObjectReference> _module;
public Interop(IJSRuntime jsRuntime)
{
_filePath = filePath;
_jsRuntime = jsRuntime;
}
public Task<IJSObjectReference> Module => _module ??= _jsRuntime.InvokeAsync<IJSObjectReference>("import", "./hello.js").AsTask();
//create a method to call function from the JS file
public async void SayHelloWorld(string name)
{
var module = await Module;
await module.InvokeVoidAsync("helloWorld", name);
}
public void Dispose()
{
if (_module is not null)
{
((IDisposable)_module).Dispose();
}
}
}
then use the js function like:
new Interop(JSRuntime).SayHelloWorld("Adam");

Application_Start event on ASP.NET Core 6.0

How would I go about setting global variables in ASP.NET Core 6.0(razor pages)?
I have some information in the database, for example, ServiceName, ContactEmail and so on, and want to save it to my static class.
I don't want to access the database every time I need to display the information.
In addition, there aren't Global.asax in ASP.NET Core .
In ASP.NET MVC 5 (based on .net framework), I could do it like
// global.asax
protected void Application_Start() {
var context = new DefaultConnection();
MyConfig.ServiceName = context.GlobalSettings.SingleOrDefault().ServiceName;
// MyConfig is my static class
}
But I don't know where I should do it in ASP.NET Core project.
How can I do that? Please help me.
So lazy-loading is probably a very good choice for you.
Step 1: Create a data service that provides your data.
public interface IStaticDbData // Think of a better name!
{
public Task<string> GetContactEmailAsync();
public Task<string> GetServiceNameAsync();
// Etc.
}
public class StaticDbData : IStaticDbData
{
// Since we want a singleton, we'll have to synchronize the data fetching.
private object _lock = new object();
private string _contactEmail;
private string _serviceName;
// Etc.
// Try to create a single function that loads all of the data in one round trip to the DB.
// This will run in its own thread, so the calling thread can be awaited.
private Task LoadAllDataAsync()
=> Task.Run(() =>
{
lock (_lock)
{
//Re-check after locking.
if (_contactEmail != null)
{
return;
}
// Database code here to extract your data.
// Save to the individual fields.
}
});
public async Task<string> GetContactEmailAsync()
{
// See if data is there.
if (_contactEmail != null)
{
return _contactEmail;
}
// Data was not there. Load data.
await LoadAllDataAsync();
return _contactEmail;
}
public async Task<string> GetServiceNameAsync()
{
if (_serviceName != null)
{
return _serviceName;
}
await LoadAllDataAsync();
return _serviceName;
}
}
Step 2: Now that you have your service interface and service implementation, register the m in the IoC container. In program.cs:
builder.Services.AddSingleton<IStaticDbData, StaticDbData>();
Step 3: Consume the service as you would any other service.
public class SomeOtherServiceOrControllerOrWhatever
{
private IStaticDbData StaticDbDataSvc { get; }
// Constructor-injected.
public SomeOtherServiceOrControllerOrWhatever(IStaticDbData staticDbDataSvc)
{
StaticDbDataSvc = staticDbDataSvc;
}
}
NOTE: Make sure that your consuming services are also registered and resolved using the IoC container.
This is sudo code
You can create a static class with static properties:
public static class MyConfig
{
public static string Setting1 {set; get;}
...
}
then write a method to fetch data from your database and fill MyConfig and in the Program.cs file just call that method:
app.MapControllers();
app.Run();
CallYourMethodHere(); <-----
another is you can do this:
first create a static class:
public static class MyConfig
{
private static Dictionary<string, string> MyConfigs {set; get;}
private static Dictionary<string, string> GetConfigFromDatabase(bool forceToFill)
{
if(MyConfigs == null || MyConfigs.Any() == false || forceToFill == true)
{
//Fetch Data From Database and Fill MyConfig
}
}
public static string GetConfig(string configName)
{
return GetConfigFromDatabase(false)[configName];
}
}
In solution 2 you have to consider some thread-safe and race condition concepts.

Is there any way to execute code once in a ASP.NET Web API REST service?

I have an ASP.NET Web API REST service and I would like to execute some code once when service is started only for first time, not each time a web api method is requested/invoked from my ASP.NET MVC application.
I would like to do this because I want to initialize an EventLog and then use it to create entries in the windows event viewer.
Is there some easy way to do it?
UPDATE:
As Jonhatan suggested in his answer I create a method within global.asax.cs:
Global.asax.cs:
namespace MyWebAPIApp
{
public class WebApiApplication : System.Web.HttpApplication
{
public MyLog _myLog;
protected void Application_Start()
{
// Here some stuff
SetupEventLogging();
}
private void SetupEventLogging()
{
if (!EventLog.SourceExists("MyWebApiLog"))
{
EventLog.CreateEventSource("MyWebApiLog", "MyWebApiLogLog");
}
EventLog eventLog = new EventLog();
eventLog.Source = "MyWebApiLog";
eventLog.Log = "MyWebApiLog";
_myLog = new MyLog(eventLog, "MyWebApiService");
}
}
}
Controller:
namespace MyWebAPIApp.Controllers
{
public class MyController : ApiController
{
public void GetAll()
{
_myLog.Success("All records read");
}
}
}
But now if I create a global variable _myLog, how can I access this variable from all the methods in my Controller in order to do _myLog.Error(...) or _myLog.Success(...)?
You would typically do that in the ApplicationStart method in your global.asax.cs:
protected void Application_Start()
{
AreaRegistration.RegisterAllAreas();
GlobalConfiguration.Configure(WebApiConfig.Register);
FilterConfig.RegisterGlobalFilters(GlobalFilters.Filters);
RouteConfig.RegisterRoutes(RouteTable.Routes);
BundleConfig.RegisterBundles(BundleTable.Bundles);
SetupLogging(); // do something in here / wire up your flavour of logging
}
Often, the pattern will be:
set up your logging on app start - this is where you set up the database connection to store the logs, etc
call a static logger.Write method throughout your code whenever you want to write to log.
I use Microsoft.Practices.EnterpriseLibrary.Logging, but I think Serilog or Log4Net are 2 probably more common frameworks now.
So, in my global.asax.cs, the SetupLogging() method is:
private static void SetupLogging()
{
var configurationSource = ConfigurationSourceFactory.Create();
DatabaseFactory.SetDatabaseProviderFactory(new DatabaseProviderFactory(configurationSource));
var logWriterFactory = new LogWriterFactory(configurationSource);
Logger.SetLogWriter(logWriterFactory.Create());
var daysToKeepLogsInDb = int.Parse(ConfigurationManager.AppSettings["DaysToKeepLogsInDb"]);
CustomLogger.PurgeLogs(daysToKeepLogsInDb); // only keep last 90 etc days of event logging in the db
CustomLogger.Write("Application Starting", TraceEventType.Information);
}
Basically just the things that the framework needs to 'get going', and a little custom cleanup. And then I have a CustomLogger class to help write entries the way I want, run a custom stored procedure to clean up old logs, etc:
using Microsoft.Practices.EnterpriseLibrary.Logging;
using System;
using System.Collections.Generic;
using System.Configuration;
using System.Data;
using System.Data.SqlClient;
using System.Diagnostics;
namespace MyApplication.Helpers
{
public class CustomLogger
{
private static readonly ICollection<string> EmptyCategoriesList = new List<string>(0);
private const string LogTitle = "MyApplication Name";
public static void Write(object message)
{
Write(message, TraceEventType.Error);
}
public static void Write(object message, TraceEventType severity)
{
Logger.Write(message, EmptyCategoriesList, -1, 1, severity, LogTitle);
}
public static void PurgeLogs(int keepLastXDays)
{
var connectionString = ConfigurationManager.ConnectionStrings["MyLoggingConnectionString"].ConnectionString;
using (var con = new SqlConnection(connectionString))
{
using (var command = new SqlCommand("PurgeLogs", con)) // custom stored procedure
{
var dateTo = DateTime.Now.AddDays(keepLastXDays * -1);
command.CommandType = CommandType.StoredProcedure;
command.Parameters.Add(new SqlParameter("#dateTo", dateTo));
command.Parameters.Add(new SqlParameter("#title", LogTitle));
con.Open();
command.ExecuteNonQuery();
con.Close(); // technically not required because in using, but leaving in case this block gets copy-pasted out of here
}
}
}
}
}
And then, within my code (controller, helper, whatever), I write tot he log through the static method in the custom logger:
public static void EndSession(Session session)
{
try
{
Logon.DoLogoff(session);
}
catch (Exception exception)
{
CustomLogger.Write(exception);
throw new Exception("Error ending session.");
}
}
If you do this with dependency injection, it would (especially) allow you to swap out your logging framework more easily, and allow you to unit test a little more easily. But you would have to create another 'layer' between your application and the logger to abstract the relationship out a bit more. You should read up on dependency injection, as it is something that is often worth using.
But now if I create a global variable _myLog, how can I access this variable from all the methods in my Controller in order to do _myLog.Error(...) or _myLog.Success(...)?
Make _myLog static and reference it WebApiApplication._myLog where WebApiApplication is application class defined in global.asax.cs.
I'd rather create some static class with MyLog static property:
public static class LogManager
{
public static MyLog Logger;
}
And in global.asax.cs in SetupEventLogging() put
LogManager.Logger = new MyLog(eventLog, "MyWebApiService");

Logging the execution of a Hangfire RecurringJob in database?

I have set up hangfire successfully for my ASP.NET project, i.e. the 11 Hangfire tables are created in my database. I tried the following command inside the Application_Start() of my project's Global.asax:
namespace myAPI
{
public class WebApiApplication : System.Web.HttpApplication
{
protected void Application_Start(
{
System.Diagnostics.Debug.WriteLine("Recurring job will be set up.");
RecurringJob.AddOrUpdate(
"some-id",
() => System.Diagnostics.Debug.WriteLine("Job instance started at " +
DateTime.Now)),
"*/2 * * * 1-5");
}
}
}
Sadly, inside Visual Studio's window Output > Debug I only see Reccuring job will be set up. and nothing ever after. However, a SELECT * FROM [myContext].[HangFire].[Set] shows me
Key Score Value ExpireAt
recurring-jobs 1579116240 some-id NULL
So far so good, this means that the job is indeed set up.
But how do I log inside my DB each and each time when the RecurringJob is executed? Do I assume correctly that Hangfire does not do that out of the box and I have to log it myself within the arrow-function? Or is there a more elegant way?
Question on the side: Why don't I see any output of System.Diagnostics.Debug.WriteLine within my recurring job?
References
Hangfire doesn't create tables in IIS
How to configure hangfire with ASP.NET to obtain connection string from config file?
Official hangfire.io docu on recurrent tasks
You can use SeriLog with Hangfire out of the box. Serilog comes with different sinks, e.g. Serilog.Sinks.MSSqlServer. You can configure it in startup.cs:
using Serilog;
using Serilog.Sinks.MSSqlServer;
Log.Logger = new LoggerConfiguration()
.WriteTo
.MSSqlServer(
connectionString: hangfireConnectionString,
tableName: "Logs",
autoCreateSqlTable: true
).CreateLogger();
// will display any issues with Serilog config. comment out in prod.
Serilog.Debugging.SelfLog.Enable(msg => Debug.WriteLine(msg));
GlobalConfiguration.Configuration
.UseSqlServerStorage(hangfireConnectionString)
.UseSerilogLogProvider();
After you schedule your job, you can log it with
Log.Information(string.Format("Hanfire Job Scheduled at {0}", DateTime.Now));
Hangfire includes a concept of job filters (similar to ASP.NET MVC's Action Filters). For your use case, you would define one that would write to your database (adjust based on your needs):
using Hangfire.Common;
using Hangfire.Server;
class LogCompletionAttribute : JobFilterAttribute, IServerFilter
{
public void OnPerforming(PerformingContext filterContext)
{
// Code here if you care when the execution **has begun**
}
public void OnPerformed(PerformedContext context)
{
// Check that the job completed successfully
if (!context.Canceled && context.Exception != null)
{
// Here you would write to your database.
// Example with entity framework:
using (var ctx = new YourDatabaseContext())
{
ctx.Something.Add(/**/);
ctx.SaveChanges();
}
}
}
}
And then apply the filter to the job method:
namespace myAPI
{
public class WebApiApplication : System.Web.HttpApplication
{
protected void Application_Start(
{
System.Diagnostics.Debug.WriteLine("Recurring job will be set up.");
RecurringJob.AddOrUpdate("some-id", () => MyJob(), "*/2 * * * 1-5");
}
[LogCompletion]
public static void MyJob()
{
System.Diagnostics.Debug.WriteLine("Job instance started at " + DateTime.Now)
}
}
}
Docs: https://docs.hangfire.io/en/latest/extensibility/using-job-filters.html
So the cron is set to fire At every 2nd minute on every day-of-week from Monday through Friday. I assume you are waiting for the job to execute and that it is in the right window of time.
Most of the references that I found on the web indicated that you can do.
RecurringJob.AddOrUpdate(() => Console.WriteLine("This job will execute once in every minute"), Cron.Minutely);
Maybe you have to line up the dots a bit better to write to the vs console.
There is also an admin portal that can be configured to see what is begin run and when.
I have the following setup.
Global.asax.cs
protected void Application_Start()
{
HangfireJobsConfig.Register();
}
public class HangfireJobsConfig
{
public static void Register()
{
if (App1Config.RunHangfireService)
{
JobStorage.Current = new SqlServerStorage(App1Config.DefaultConnectionStringName.Split('=').Last());
GlobalConfiguration.Configuration.UseConsole();
RecurringJob.AddOrUpdate("RunJob1", () => RunJob1(null), Cron.MinuteInterval(App1Config.RunJob1Interval));
RecurringJob.AddOrUpdate("RunJob2", () => RunJob2(null), Cron.MinuteInterval(App1Config.RunJob2Interval));
}
}
[AutomaticRetry(Attempts = 0, Order = 1)]
public static void RunJob1(PerformContext context)
{
//dostuff
}
[AutomaticRetry(Attempts = 0, Order = 2)]
public static void RunJob2(PerformContext context)
{
//do stuff
}
}
Startup.cs
public partial class Startup
{
public void Configuration(IAppBuilder app)
{
ConfigureAuth(app);
ConfigureHangFire(app);
}
public void ConfigureHangFire(IAppBuilder app)
{
if (App1Config.RunHangfireService)
{
GlobalConfiguration.Configuration.UseSqlServerStorage(
AppiConfig.DefaultConnectionStringName.Split('=').Last());
GlobalConfiguration.Configuration.UseConsole();
app.UseHangfireServer();
var options = new DashboardOptions
{
AuthorizationFilters = new[]
{
new AuthorizationFilter { Roles = "Inventory" }
}
};
app.UseHangfireDashboard("/hangfire", options);
}
}
}
The actual problem was a very trivial one, the initialization of the actual background server was missing BackgroundJobServer();. Here the fully functional code:
namespace myAPI
{
public class WebApiApplication : System.Web.HttpApplication
{
protected void Application_Start()
{
string connString = ConfigurationManager.ConnectionStrings["myContext"].ToString();
Hangfire.GlobalConfiguration.Configuration.UseConsole();
Hangfire.GlobalConfiguration.Configuration.UseSqlServerStorage(connString,
new SqlServerStorageOptions {
CommandBatchMaxTimeout = TimeSpan.FromMinutes(5),
SlidingInvisibilityTimeout = TimeSpan.FromMinutes(5),
QueuePollInterval = TimeSpan.Zero,
UseRecommendedIsolationLevel = true,
UsePageLocksOnDequeue = true,
DisableGlobalLocks = true
});
var bgndJS = new BackgroundJobServer(); // <--- this is essential!
RecurringJob.AddOrUpdate("myRecurringJob", () => HangfireRecurringJob(), "*/2 * * * 1-5");
System.Diagnostics.Debug.WriteLine("---> RecurringJob 'myHangfireJob' initated.");
}
public void HangfireRecurringJob() {
System.Diagnostics.Debug.WriteLine("---> HangfireRecurringJob() executed at" + DateTime.Now);
Console.Beep(); // <-- I was really happy to hear the beep
}
}
}

Different results for Request.Url.AbsoluteUri, default.aspx missing

I have a http module which redirects to a website when a user is not authorised. This website then checks for credentials and returns the user to the original page based on a query string.
The problem I have is that Request.Url.AbsoluteUri seems to omit default.aspx whenever a root directory is requested, e.g. http://example/application/
This behaviour can be observed using the test case below. When using Response.Redirect within Application_AuthenticateRequest
Please note, the VS web development server Cassini behaves normally and will correctly redirect to http://example/application/?url=http://example/application/default.aspx I presume this is related to IIS processing the request differently. (I'm running IIS6)
namespace HI.Test {
public class Authentication : IHttpModule {
private HttpRequest Request { get { return HttpContext.Current.Request; } }
private HttpResponse Response { get { return HttpContext.Current.Response; } }
private Cache Cache { get { return HttpContext.Current.Cache; } }
public void Init(HttpApplication application) {
application.AuthenticateRequest += (new EventHandler(Application_AuthenticateRequest));
}
private void Application_AuthenticateRequest(Object source, EventArgs e) {
if (Request.QueryString["url"] == null) {
Cache.Insert("URLRedirected", Request.Url.AbsoluteUri);
Response.Redirect(Request.Url.AbsoluteUri + "?url=" + Request.Url.AbsoluteUri);
}
}
public void Dispose() {
}
}
}
I'm obviously looking for a fix to the problem and also I'd like to understand why this happens.

Categories

Resources