HangfireIO / Hangfire

An easy way to perform background job processing in .NET and .NET Core applications. No Windows Service or separate process required
https://www.hangfire.io
Other
9.25k stars 1.68k forks source link

How to search job by 'Arguments'? #988

Open sadiqkhoja opened 6 years ago

sadiqkhoja commented 6 years ago

Is there any way to search job by 'Arguments'? Using JobStorage.Current.GetMonitoringApi or getting Hangfire DBContext and doing manual query.

pieceofsummer commented 6 years ago

Not possible with the current storage API. You can only get a full job list (via IMonitoringApi) and then filter it.

Manual database query also is a way to go, but you'll need to rewrite the code if you change the storage provider.

sadiqkhoja commented 6 years ago

So which method returns Hangfire DBContext? Or I have to reconstruct it?

pieceofsummer commented 6 years ago

Hangfire doesn't use EF, so there's no "DBContext" there. But you may use whatever way to access the storage database.

dev-mfm commented 9 months ago

Is this issue still exists or there are new options to query jobs by parameters. Can I create my own DbContext using the same Hangfire connection string to search jobs by parameters.

edelciomolina commented 5 days ago

@sadiqkhoja a created a "Dispatcher Class". The idea is to use the querystring to search by MethodName or Arguments, ex:

http://localhost:61872/search?name=YourMethodName
http://localhost:61872/search?args=PartOfArgument
http://localhost:61872/search?name=YourMethodName&args=PartOfArgument

So you will have an output like: image

Into your ´Configuration(IAppBuilder app)´, put this:

DashboardRoutes.Routes.Add("/search", new SearchDispatcher());

And create a new class like that below: (Of course you will have to make some changes, how like your way to connect and fetch data from hangfire tables)

using System.Collections.Generic;
using System.Data;
using System.Threading.Tasks;
using Hangfire.Annotations;
using Hangfire.Dashboard;
using Library;
using Npgsql;
using Scriban;
using System.Web;

namespace ServerProcess.Dispatchers
{
    internal sealed class SearchDispatcher : IDashboardDispatcher
    {

        public SearchDispatcher() { }

        public async Task Dispatch([NotNull] Hangfire.Dashboard.DashboardContext context)
        {

            string name = context.Request.GetQuery("name") + "";
            string args = context.Request.GetQuery("args") + "";
            string baseUrl = HttpContext.Current.Request.Url.AbsoluteUri.Replace(HttpContext.Current.Request.Url.PathAndQuery, "");

            DataTable data = GetJobs(baseUrl, name, args);
            string html = GetHtml(data);
            await WriteHtml(context, html);

        }

        private DataTable GetJobs(string baseUrl, string name, string args)
        {

            string sql = $@"
                    SELECT id, 
                           job.CreatedAt createdat,   
                           '{baseUrl}/jobs/details/' || CAST(id AS text) AS url, 
                           job.StateName statename,   
                           invocationdata->>'Method' methodname,
                           invocationdata->>'Arguments' arguments
                      FROM Hangfire.Job job
                     WHERE ( job.InvocationData::text LIKE '%' || @name || '%'
                           AND job.Arguments::text LIKE '%' || @args || '%' )
                       AND NOT invocationdata::text LIKE '%Storage%' -- Remove to see recurring jobs!
                     ORDER BY createdat DESC 
                     LIMIT 100
                ";

            using (NpgsqlCommand cmd = new NpgsqlCommand(sql))
            {
                cmd.Parameters.AddWithValue(nameof(name), name);
                cmd.Parameters.AddWithValue(nameof(args), args);

                using (DatabasePostgres database = new DatabasePostgres(DatabasePostgres.EnumSourceConnections.Hangfire))
                {
                    return database.ReturnDataTable(cmd);

                }

            }

        }

        private string GetHtml(DataTable data)
        {

            var template = @"
                <style>
                    .main { display: flex; flex-direction: column; font-family: system-ui; font-size: 12px; }
                    .row { display: flex; }
                    .col { padding: 10px; border: 1px solid #ddd; text-align: center; }
                    .col.date { min-width: 80px; }
                    .col.id { min-width: 100px; }
                    .col.statename { min-width: 100px; }
                    .col.method { min-width: 220px;text-align: left;word-wrap: break-word; }
                    .col.arguments { width: 100%; overflow-wrap: anywhere; text-align: left; }
                    .header { font-weight: bold; }
                </style>

                <div class=""main"">
                  <div class=""row header"">
                    <div class=""col date"">Created At</div>
                    <div class=""col id"">Id</div>
                    <div class=""col statename"">StateName</div>
                    <div class=""col method"">Method</div>
                    <div class=""col arguments"">Arguments</div>
                  </div>
                  {{ for row in rows }}
                  <div class=""row"">
                    <div class=""col date"">{{ row.CreatedAt }}</div>
                    <div class=""col id""><a href=""{{ row.Url }}"">{{ row.Id }}</a></div>
                    <div class=""col statename"">{{ row.StateName }}</div>
                    <div class=""col method"">{{ row.Method }}</div>
                    <div class=""col arguments"">{{ row.Arguments }}</div>
                  </div>
                  {{ end }}
                </div>
            ";

            var rows = new List<dynamic>();
            foreach (DataRow row in data.Rows)
            {
                var rowData = new
                {
                    Id = row["id"].ToString(),
                    CreatedAt = row["createdat"].ToString(),
                    StateName = row["statename"].ToString(),
                    Method = row["methodname"].ToString(),
                    Arguments = row["arguments"].ToString(),
                    Url = row["url"].ToString()
                };
                rows.Add(rowData);
            }

            var dataModel = new { rows = rows }; 
            var script = Template.Parse(template);
            var result = script.Render(dataModel, member => member.Name);

            return result;

        }

        private async Task WriteHtml([NotNull] Hangfire.Dashboard.DashboardContext context, string html)
        {

            context.Response.ContentType = "text/html";
            await context.Response.WriteAsync(html);

        }
    }

}