IT rekvalifikace s garancí práce. Seniorní programátoři vydělávají až 160 000 Kč/měsíc a rekvalifikace je prvním krokem. Zjisti, jak na to!
Hledáme nové posily do ITnetwork týmu. Podívej se na volné pozice a přidej se do nejagilnější firmy na trhu - Více informací.

Diskuze: Ověření dostupnosti stovek webů

V předchozím kvízu, Test znalostí C# .NET online, jsme si ověřili nabyté zkušenosti z kurzu.

Aktivity
Avatar
Adam Gajdečka:30.10.2018 7:58

Potřebuji ověřit dostupnost daných webových stránek a uložit výsledek do databáze pomocí Entity Framework. Aplikace je webová, Core 2.1

Ověřovat se musí každou minutu a webových adres k ověření je 100+. Prosím o radu, jak vykonat tyto ověření tak, aby opravdu každou minutu byl přidán nový záznam o dostupnosti.

Koupil jsem si zde seriál o paralelním programování, ale zatím moc chytrý z toho nejsem. Zkoušel jsem metodu public void CheckWebsite(Web­site web) udělat async, ale to neřešilo problém.

Díky

Zkusil jsem:

public void CheckWebsites()
      {
          foreach (var web in _dbContext.Websites.ToList())
          {
              CheckWebsite(web);
          }
      }
public void CheckWebsite(Website web)
{

    WebsiteStateRecord record = new WebsiteStateRecord();
    record.WebsiteId = web.Id;
    record.Date = DateTime.Now;

    HttpWebRequest request = (HttpWebRequest)HttpWebRequest.Create(web.Url);
    request.AllowAutoRedirect = true;
    request.Timeout = 30000;
    request.Method = "HEAD";

                 try
    {
        HttpWebResponse httpRes = (HttpWebResponse)request.GetResponse();
        record.HttpStatusCode = (int)httpRes.StatusCode;
        record.Online = true;

    }
    catch (WebException we)
    {
        try
        {
            record.HttpStatusCode = (int)((HttpWebResponse)we.Response).StatusCode;

        }
        catch (Exception)
        {

            //   throw;
        }

        record.Online = false;
    }


    _dbContext.WebsiteStateRecords.Add(record);
    _dbContext.SaveChanges();
}
 
Odpovědět
30.10.2018 7:58
Avatar
Adam Gajdečka:30.10.2018 7:59

Jak spouštět kód každou minutu vím, jde mi jen o vykonání v co nejrychlejším termínu. Díky

 
Nahoru Odpovědět
30.10.2018 7:59
Avatar
Filip Zeman
Tvůrce
Avatar
Filip Zeman:30.10.2018 9:32

No můžu ti zkusit ukázat rozdíl. Kód je pouze ukázkový, ale na svůj projekt si ho převedeš jednoduše.

Takhle by teoreticky vypadalo stahování nějaké stránky async. Celkový čas by byl cca. stejný. U tohoto čekáme, než se stáhne jedna stránka, pak stahujeme druhou, atd.

private async Task DownloadAsync()
{
        List<string> websites PrepData();

        foreach (string web in websites)
        {
        WebsiteDataModel results = await Task.Run(() => DownloadWebsite(web));
        ReportWeb(results)
        }
}

Zrychlit bychom to mohli tak, že budeme stahovat všechny současně a čekat budeme jenom na tu nejdelší.

private async Task DownloadAsyncParallel()
{
        List<string> websites PrepData();
        List<Task<WebsiteDataModel>> tasks = new List<Task<WebsiteDataModel>>();

        foreach (string web in websites)
        {
                tasks.Add(Task.Run(() => DownloadWebsite(web)));
        }
        var results = await Task.WhenAll(tasks);

        foreach (var item in results)
        {
                        ReportWeb(results);
        }
}

Nebo tohle všechno vysvětluje ještě tohle video: https://www.youtube.com/watch?… . Rozhodně stojí za to se na něho podívat, řeší tam skoro stejný problém.

 
Nahoru Odpovědět
30.10.2018 9:32
Avatar
Odpovídá na Adam Gajdečka
don.jarducius:30.10.2018 12:06

Ahoj,
nad Listem, potažmo nad IEnumerable je možnost volat AsParallel(), potom lze vykonávat parallelně operace s Listem, např. .ForAll(x=> { /* obsluha práce s položkou */ }); Automaticky by se měl spustit efektivní počet vláken vzhledem k dostupným jádrům.
Problém ale může být při pokusu o zápis do databáze pomocí entit - paralelní přístup ke kolekcím což je problém. Tomu se dá vyhnout buď pomocí zámků, nebo pomocí DbContext.Data­base.ExecuteS­qlCommand … a vkládat data přímo pomocí SQL.

Nahoru Odpovědět
30.10.2018 12:06
Ten kdo nechce hledá důvod, ten kdo chce hledá způsob
Avatar
Adam Gajdečka:1.11.2018 9:59

díky za rady. Během víkendu to budu zkoumat

 
Nahoru Odpovědět
1.11.2018 9:59
Avatar
Odpovídá na Filip Zeman
Adam Gajdečka:2.11.2018 12:34

Povedlo se mi to upravit podle tvého příkladu a je to rychlé jako blesk. Jen nevím, jak to každou minutu spustit, používám HangFire.

public async Task CheckWebsitesAsync()
      {
          List<Task<WebsiteStateRecord>> tasks = new List<Task<WebsiteStateRecord>>();

          foreach (var web in _dbContext.Websites.ToList())
          {
              tasks.Add(Task.Run(() => CheckWebsiteAsync(web)));
          }
          var results = await Task.WhenAll(tasks);

          _dbContext.WebsiteStateRecords.AddRange(results);
          _dbContext.SaveChanges();
      }

Toto mi shodí web, začne neodpovídat a potom hodí Bad Request

RecurringJob.AddOrUpdate(() => _websiteBLL.CheckWebsitesAsync(), Cron.MinuteInterval(1));

Díky

 
Nahoru Odpovědět
2.11.2018 12:34
Avatar
Adam Gajdečka:2.11.2018 15:52

Co jsem načetl, tak Hangfire nepodporuje async joby.(ale podporuje to ta Pro verze, která ovšem stojí 500 USD ročně) Jak to tedy nejlépe spouštět? Nejraději nějakou alternativu k Hangfire, ale co jsem našel, tak nebylo moc friendly.

 
Nahoru Odpovědět
2.11.2018 15:52
Avatar
Odpovídá na Adam Gajdečka
don.jarducius:2.11.2018 20:23

Jsem trochu zmatený, kde a jak hostuješ tu svoji aplikaci? Hosting/VPS/Housing ?

Nahoru Odpovědět
2.11.2018 20:23
Ten kdo nechce hledá důvod, ten kdo chce hledá způsob
Avatar
 
Nahoru Odpovědět
2.11.2018 21:04
Avatar
don.jarducius:3.11.2018 19:16

Promiň, ale v tom případě nechápu, proč si to komplikuješ s Hangfire.
Vytvoř si konzolovou aplikaci (resp. obal si "očuchávací" funkci konzolovkou), která při spuštění "očuchá" potřebné weby a nastav si její spouštění do standardního plánovače úloh.
Nebo si vytvoř windows service, který se o to bude starat - což je jen pár řádků navíc oproti konzolovce...

Nahoru Odpovědět
3.11.2018 19:16
Ten kdo nechce hledá důvod, ten kdo chce hledá způsob
Avatar
Odpovídá na don.jarducius
Adam Gajdečka:3.11.2018 19:22

Hangfire bylo nejsnadnější použít v mých projektech. Rád bych tedy to udělal, jak popisuješ. Vůbec netuším jak začít. Jak spustit z consolové aplikace kód v mé webové aplikaci? Nemáš nějaké odkazy na video či nějaké tutorialy? Děkuji

 
Nahoru Odpovědět
3.11.2018 19:22
Avatar
don.jarducius:3.11.2018 21:49

Prostě ten kód zkopíruješ do té konzolovky. Klasický net framework ti buidle exe, pokud chceš použít core, ten vy vybuilduje dll, které spustíš pomocí příkazu dotnet [název].dll.
Trochu jsem to zjednodušil, teď by to defakto mělo jít i v HangFire.
Kód konzolovky v net core

using System;
using System.Diagnostics;
using System.Linq;
using System.Net;

namespace ConsoleApp4
{
    class Program
    {
        static void Main(string[] args)
        {
            using (var c = new Checker())
            {
                c.CheckWebsites();
            }
        }
    }

    public class Checker : IDisposable
    {
        private WebSiteDbContext _dbContext;
        public Checker()
        {
            _dbContext = new WebSiteDbContext();
        }
        public void Dispose() {
            _dbContext.Dispose();
        }

        public void CheckWebsites()
        {
            var weby = _dbContext.Websites.ToList();
            var results = weby.AsParallel().Select(x => CheckWebsiteAsync(x)).ToList();

            _dbContext.WebsiteStateRecords.AddRange(results);
            _dbContext.SaveChanges();
        }

        public WebsiteStateRecord CheckWebsiteAsync(Website web)
        {
            WebsiteStateRecord record = new WebsiteStateRecord
            {
                WebsiteId = web.Id,
                Date = DateTime.Now
            };
            HttpWebRequest request = (HttpWebRequest)HttpWebRequest.Create(web.Url);
            request.AllowAutoRedirect = true;
            request.Timeout = 30000;
            request.Method = "HEAD";

            try
            {
                HttpWebResponse httpRes = (HttpWebResponse)request.GetResponse();
                record.HttpStatusCode = (int)httpRes.StatusCode;
                record.Online = true;
            }
            catch (WebException we)
            {
                try
                {
                    record.HttpStatusCode = (int)((HttpWebResponse)we.Response).StatusCode;
                }
                catch (Exception ex)
                {
                    Console.WriteLine($"Url: {web.Url} - chyba: {ex.Message}");
                    Debug.WriteLine(ex.Message);
                    Debug.WriteLine(ex.StackTrace);
                }

                record.Online = false;
            }
            return record;
        }
    }
}
Nahoru Odpovědět
3.11.2018 21:49
Ten kdo nechce hledá důvod, ten kdo chce hledá způsob
Avatar
Odpovídá na don.jarducius
Adam Gajdečka:4.11.2018 10:36

Díky. Vytvořil jsem tedy Konzolovou aplikaci v Core a kód mám tedy:

using RestSharp;
using MyApp.Areas.Websites.Models;
using MyApp.Data;
using System;
using System.Collections.Generic;
using System.Linq;
using System.Net;
using System.Threading.Tasks;

namespace CheckWebsitesConsole
{
    class Program
    {
        static void Main(string[] args)
        {
            using (var c = new Checker())
            {
                c.CheckWebsitesAsync();
            }
        }
    }

    public class Checker : IDisposable
    {
        private ApplicationDbContext _dbContext;

        public Checker()
        {
            _dbContext = new ApplicationDbContext();
        }

        public void Dispose()
        {
            _dbContext.Dispose();
        }

        public async Task<bool> CheckWebsitesAsync()
        {
            List<Task<WebsiteStateRecord>> tasks = new List<Task<WebsiteStateRecord>>();

            foreach (var web in _dbContext.Websites.ToList())
            {
                tasks.Add(Task.Run(() => CheckWebsiteAsync(web)));
            }
            var results = await Task.WhenAll(tasks);

            _dbContext.WebsiteStateRecords.AddRange(results);
            _dbContext.SaveChanges();
            return true;
        }


        public async Task<WebsiteStateRecord> CheckWebsiteAsync(Website web)
        {
            WebsiteStateRecord record = new WebsiteStateRecord();
            record.WebsiteId = web.Id;
            record.Date = DateTime.Now;

            List<HttpStatusCode> statusCodes = new List<HttpStatusCode>();
            statusCodes.Add(HttpStatusCode.Found);
            statusCodes.Add(HttpStatusCode.Moved);
            statusCodes.Add(HttpStatusCode.MovedPermanently);
            statusCodes.Add(HttpStatusCode.Redirect);
            statusCodes.Add(HttpStatusCode.PermanentRedirect);
            statusCodes.Add(HttpStatusCode.TemporaryRedirect);
            statusCodes.Add(HttpStatusCode.RedirectMethod);
            statusCodes.Add(HttpStatusCode.RedirectKeepVerb);

            try
            {
                var client = new RestClient(web.Url);
                var request = new RestRequest(Method.HEAD);

                // execute the request
                IRestResponse response = client.Execute(request);
                record.HttpStatusCode = (int)response.StatusCode;
                if (statusCodes.Contains(response.StatusCode))
                {
                    string url1 = response.Headers.SingleOrDefault(s => s.Name == "Location").Value.ToString();
                    bool run = true;
                    while (run)
                    {
                        var client1 = new RestClient(url1);
                        var request1 = new RestRequest(Method.HEAD);

                        // execute the request
                        IRestResponse response1 = client1.Execute(request);

                        record.HttpStatusCode = (int)response1.StatusCode;
                        if (statusCodes.Contains(response1.StatusCode))
                        {
                            url1 = (response1.Headers.SingleOrDefault(s => s.Name == "Location").Value.ToString());
                        }
                        else if (response1.StatusCode == HttpStatusCode.OK)
                        {
                            record.Online = true;
                            break;
                        }
                        else
                        {
                            record.Online = false;
                            break;
                        }
                    }
                }
                else if (response.StatusCode == HttpStatusCode.OK)
                {
                    record.Online = true;
                }
                else
                {
                    record.Online = false;
                }
            }
            catch (Exception)
            {
            }
            return record;
        }

    }
}

A teď si tedy vytvořím v plánovači úlohu. která se bude spouštět: dotnet CheckWebsites­Console.dll ? Je to tak? Díky

 
Nahoru Odpovědět
4.11.2018 10:36
Avatar
Odpovídá na Adam Gajdečka
don.jarducius:4.11.2018 11:29

Ano, je to tak, při vytváření úlohy v plánovači ti wizzard nabídne základ, další funkce ti nabídne až při editaci úlohy. Nastav si potom kompatibilitu úlohy na ws2016, spustit nezávislé na přihlášení uživatele. Aktivační událostí si potom nastav 5 po jedné minutě s opakováním po 5 minutách po dobu 1 dne

Nahoru Odpovědět
4.11.2018 11:29
Ten kdo nechce hledá důvod, ten kdo chce hledá způsob
Avatar
Odpovídá na don.jarducius
Adam Gajdečka:4.11.2018 14:22

Nemůžu to zkompilovat, bohužel. Chyba:

Severity Code Description Project File Line Suppression State
Error CS7036 There is no argument given that corresponds to the required formal parameter 'options' of 'ApplicationDbCon­text.Applicati­onDbContext(DbCon­textOptions<Ap­plicationDbCon­text>)' CheckWebsites­Console F:\Projekty\My­App\Solution\My­App\CheckWebsi­tesConsole\Pro­gram.cs 29 Active

public Checker()
      {
          _dbContext = new ApplicationDbContext(); //tady je chyba
      }

:

public class ApplicationDbContext : IdentityDbContext
   {

       public ApplicationDbContext(DbContextOptions<ApplicationDbContext> options)
           : base(options)
       {
       }


       //website
       public DbSet<Website> Websites { get; set; }

       public DbSet<WebsiteStateRecord> WebsiteStateRecords { get; set; }


   }
Editováno 4.11.2018 14:23
 
Nahoru Odpovědět
4.11.2018 14:22
Avatar
Odpovídá na Adam Gajdečka
don.jarducius:4.11.2018 19:35

Níže je funkční a otestovaná .net core konzolovka, problém při kompilaci je Dědění jakéhosi IdentityDbContextu namísto DbContextu z Microsoft.Enti­tyFrameworkCo­re.

Nevím jaké další sloupce a tabulky máš v db, napsal jsem minium. Minimálně si přepis connectionString

DbContext:

using System;
using Microsoft.EntityFrameworkCore;

namespace ConsoleApp4
{
    public class WebSiteDbContext : DbContext
    {
        public WebSiteDbContext() : base()
        {
        }

        protected override void OnConfiguring(DbContextOptionsBuilder optionsBuilder)
        {
            optionsBuilder.UseSqlServer("server=(local);uid=WebSiteChecker;pwd=TajneHeslo;Initial Catalog=WebSiteChecker");
        }

        protected override void OnModelCreating(ModelBuilder modelBuilder)
        {
            base.OnModelCreating(modelBuilder);
            modelBuilder.Entity<WebsiteStateRecord>().ToTable("WebsiteStateRecord").HasKey(p =>p.rowid);
            modelBuilder.Entity<WebsiteStateRecord>().Property(p => p.rowid).ValueGeneratedOnAdd();
            modelBuilder.Entity<Website>().ToTable("Website").HasKey(p => p.Id);
            modelBuilder.Entity<Website>().Property(p => p.Id).ValueGeneratedOnAdd();

        }

        public DbSet<WebsiteStateRecord> WebsiteStateRecords { get; set; }
        public DbSet<Website> Websites { get; set; }
    }

    public class WebsiteStateRecord
    {
        public int rowid { get; set; }
        public int WebsiteId { get; set; }
        public DateTime Date { get; set; }
        public int HttpStatusCode { get; set; }
        public bool Online { get; set; }
    }

    public class Website
    {
        public int Id { get; set; }
        public string Url { get; set; }
    }
}

program.cs

using System;
using System.Diagnostics;
using System.Linq;
using System.Net;

namespace ConsoleApp4
{
    class Program
    {
        static void Main(string[] args)
        {
            using (var c = new Checker())
            {
                c.CheckWebsites();
            }
        }
    }

    public class Checker : IDisposable
    {
        private WebSiteDbContext _dbContext;
        public Checker()
        {
            _dbContext = new WebSiteDbContext();
        }
        public void Dispose() {
            _dbContext.Dispose();
        }

        public void CheckWebsites()
        {
            var weby = _dbContext.Websites.ToList();
            var results = weby.AsParallel().Select(x => CheckWebsiteAsync(x)).ToList();

            _dbContext.WebsiteStateRecords.AddRange(results);
            _dbContext.SaveChanges();
        }

        public WebsiteStateRecord CheckWebsiteAsync(Website web)
        {
            WebsiteStateRecord record = new WebsiteStateRecord
            {
                WebsiteId = web.Id,
                Date = DateTime.Now
            };
            HttpWebRequest request = (HttpWebRequest)HttpWebRequest.Create(web.Url);
            request.AllowAutoRedirect = true;
            request.Timeout = 30000;
            request.Method = "HEAD";

            try
            {
                HttpWebResponse httpRes = (HttpWebResponse)request.GetResponse();
                record.HttpStatusCode = (int)httpRes.StatusCode;
                record.Online = true;
            }
            catch (WebException we)
            {
                try
                {
                    record.HttpStatusCode = (int)((HttpWebResponse)we.Response).StatusCode;
                }
                catch (Exception ex)
                {
                    Console.WriteLine($"Url: {web.Url} - chyba: {ex.Message}");
                    Debug.WriteLine(ex.Message);
                    Debug.WriteLine(ex.StackTrace);
                }

                record.Online = false;
            }
            return record;
        }
    }
}
Akceptované řešení
+20 Zkušeností
+2,50 Kč
Řešení problému
Nahoru Odpovědět
4.11.2018 19:35
Ten kdo nechce hledá důvod, ten kdo chce hledá způsob
Avatar
Odpovídá na Adam Gajdečka
don.jarducius:4.11.2018 19:38

Z nugetu si doimportuj:
Microsoft.Enti­tyFrameworkCo­re
Microsoft.Enti­tyFrameworkCo­re.SqlServer

Nahoru Odpovědět
4.11.2018 19:38
Ten kdo nechce hledá důvod, ten kdo chce hledá způsob
Avatar
Odpovídá na don.jarducius
Adam Gajdečka:4.11.2018 20:19

Fantastické! Fakt to funguje! Já vůbec netušil, že se to takto dá dělat :-D

Když potřebuji více "Jobů" spouštět v rámci jednoho projektu, v různé časy, tak si mám takových konzolových aplikací tedy vytvořit několik, je to tak?

A díky za pomoc, naučil jsem se něco nového konečně

 
Nahoru Odpovědět
4.11.2018 20:19
Avatar
don.jarducius:4.11.2018 21:56

Tak záleží na tom, jestli dělají něco jiného, pokud dělají to samé, můžeš použít tuto třeba 50x když budeš chtít :)

Nahoru Odpovědět
4.11.2018 21:56
Ten kdo nechce hledá důvod, ten kdo chce hledá způsob
Avatar
Odpovídá na don.jarducius
Adam Gajdečka:6.11.2018 10:31

Na mém PC všechno funguje, ale když jsem to právě nahrál na server, vytvořil úlohu stejným způsobem, tak se dostanu na error 0x80008083 (zobrazuje se v kolonce Last Run Result v Task Scheduler).

Zkusil jsem nainstalovat .NET Core 2.1 Runtime, ale to nepomohlo. Oprávnění k souboru by také mělo být správně nastaveno.

Bohužel k tomuto erroru jsem nebyl schopen nic dohledat.

A v historii je Task Scheduler successfully completed task.
A následně Task Scheduler successfully finished.

Díky za pomoc

 
Nahoru Odpovědět
6.11.2018 10:31
Avatar
Odpovídá na Adam Gajdečka
Adam Gajdečka:6.11.2018 10:46

už jsem na to přišel, nakopíroval jsem na server jenom to .dll, bylo potřeba tam nakopírovat vše

 
Nahoru Odpovědět
6.11.2018 10:46
Děláme co je v našich silách, aby byly zdejší diskuze co nejkvalitnější. Proto do nich také mohou přispívat pouze registrovaní členové. Pro zapojení do diskuze se přihlas. Pokud ještě nemáš účet, zaregistruj se, je to zdarma.

Zobrazeno 21 zpráv z 21.