{"id":37038331,"url":"https://github.com/sontx/blackcat","last_synced_at":"2026-01-14T04:33:35.534Z","repository":{"id":13817614,"uuid":"217575851","full_name":"sontx/blackcat","owner":"sontx","description":"A bundle of dotnet utilities","archived":false,"fork":false,"pushed_at":"2022-12-08T09:17:41.000Z","size":111,"stargazers_count":7,"open_issues_count":2,"forks_count":1,"subscribers_count":1,"default_branch":"master","last_synced_at":"2025-11-27T13:48:19.249Z","etag":null,"topics":["crash-reporting","eventbus","intercommunication","ioc","settings","utility"],"latest_commit_sha":null,"homepage":null,"language":"C#","has_issues":true,"has_wiki":null,"has_pages":null,"mirror_url":null,"source_name":null,"license":null,"status":null,"scm":"git","pull_requests_enabled":true,"icon_url":"https://github.com/sontx.png","metadata":{"files":{"readme":"README.md","changelog":null,"contributing":null,"funding":null,"license":null,"code_of_conduct":null,"threat_model":null,"audit":null,"citation":null,"codeowners":null,"security":null,"support":null}},"created_at":"2019-10-25T16:40:33.000Z","updated_at":"2022-06-12T05:20:57.000Z","dependencies_parsed_at":"2023-01-11T20:21:20.578Z","dependency_job_id":null,"html_url":"https://github.com/sontx/blackcat","commit_stats":null,"previous_names":[],"tags_count":0,"template":false,"template_full_name":null,"purl":"pkg:github/sontx/blackcat","repository_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/sontx%2Fblackcat","tags_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/sontx%2Fblackcat/tags","releases_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/sontx%2Fblackcat/releases","manifests_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/sontx%2Fblackcat/manifests","owner_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/owners/sontx","download_url":"https://codeload.github.com/sontx/blackcat/tar.gz/refs/heads/master","sbom_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/sontx%2Fblackcat/sbom","scorecard":null,"host":{"name":"GitHub","url":"https://github.com","kind":"github","repositories_count":286080680,"owners_count":28409514,"icon_url":"https://github.com/github.png","version":null,"created_at":"2022-05-30T11:31:42.601Z","updated_at":"2026-01-14T01:52:23.358Z","status":"online","status_checked_at":"2026-01-14T02:00:06.678Z","response_time":107,"last_error":null,"robots_txt_status":"success","robots_txt_updated_at":"2025-07-24T06:49:26.215Z","robots_txt_url":"https://github.com/robots.txt","online":true,"can_crawl_api":true,"host_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub","repositories_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories","repository_names_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repository_names","owners_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/owners"}},"keywords":["crash-reporting","eventbus","intercommunication","ioc","settings","utility"],"created_at":"2026-01-14T04:33:34.742Z","updated_at":"2026-01-14T04:33:35.528Z","avatar_url":"https://github.com/sontx.png","language":"C#","funding_links":[],"categories":[],"sub_categories":[],"readme":"# blackcat\n\n![](https://api.travis-ci.org/sontx/blackcat.svg?branch=master)\n\nA bundle of dotnet utilities\n\n![](https://4.bp.blogspot.com/-hN5iDUgWRJE/XcrZ5SqCzHI/AAAAAAAAZb4/FnuR327jKSkWBzXF65Pv542KpEA2uS6KACLcBGAsYHQ/s1600/Untitled%2BDiagram%2B%25281%2529.png)\n\n# Getting started\n\n1. Nuget:\n\nCore library\n```bash\nInstall-Package Blackcat -Version 1.0.1\n```\nSome utilities for WinForm flatform\n```bash\nInstall-Package Blackcat.WinForm -Version 1.0.0\n```\n\n2. Dll files (comming soon): [release page](https://github.com/sontx/blackcat/releases)\n3. Clone this repo as a submodule and add reference to your .net project\n\n# All important utilities\n\n## Configuration\n\nSaves or loads configurations from file automatically.\n\n### Basic usage\n\n1. Defines your configuration class\n```cs\npublic class MyConfig\n{\n  public string Config1 {get;set;}\n  public int Config2 {get;set;}\n}\n```\n\n2. Gets configuration and use it\n```cs\n// MyConfig will be loaded from file or create new one if needed\nvar myConfig = ConfigLoader.Default.Get\u003cMyConfig\u003e();\ntextBox1.Text = myConfig.Config1;\n```\n\n3. Saves configuration: your configuration will be saved to json file automatically when winform application's closed.\nJson file will be something like:\n```json\n{\n  \"Metadata\": {\n    \"Modified\": \"2019-11-11T14:29:35.3268223+07:00\"\n  },\n  \"Configs\": [\n    {\n      \"Key\": \"MyObject\",\n      \"Data\": {\n        \"Config1\": \"This is a string config\",\n        \"Config2\": 123,\n      }\n    }\n  ]\n}\n```\n\n### Advance usage\n\nSaves immediately whenever config property changes\n\n```cs\n// Inherits from AutoNotifyPropertyChanged class and marks properties as virtual.\npublic class MyConfig : AutoNotifyPropertyChanged\n{\n  public virtual string Config1 {get;set;}\n  public virtual int Config2 {get;set;}\n}\n\n// Change save mode to:\n//  OnChange: save immediately when config changes\n//  OnExit: save when app's closed\n//  ReadOnly: don't save\nConfigLoader.Default.SaveMode = SaveMode.OnChange;\nvar myConfig = ConfigLoader.Default.Get\u003cMyConfig\u003e();\nmyConfig.Config1 = \"New config\";// config will be saved after this call\n```\n\nEdits config at runtime\n\n```cs\n[ConfigClass(Description = \"Describe your config here\")]\npublic class MyConfig\n{\n    [Description(\"First string config\")]\n    public string Config1 { get; set; }\n\n    [Description(\"Second int config\")]\n    public int Config2 { get; set; }\n}\n\nvar myConfig = ConfigLoader.Default.Get\u003cMyConfig\u003e();\nusing (var form = new SettingsForm { Settings = myConfig })\n{\n    form.ShowDialog(this);\n    var changed = form.SettingsChanged;\n}\n```\n![Edit config form](https://2.bp.blogspot.com/-LjpeTdWCyto/Xcpu8IG9gTI/AAAAAAAAZbY/LJCu-7O1uDYBM5YSAxqkJ1CIR7jjKiTEgCLcBGAsYHQ/s1600/Capture.PNG)\n\n## EventBus\n\nLightweight event aggregator/messenger for loosely coupled communication\n\n### Basic usage\n\n1. Defines an event which will carry data from caller to subscriber\n```cs\npublic class MyEvent\n{\n    public string Data1 {get; set;}\n    public int Data2 {get; set;}\n}\n```\n\n2. Subscribes events\n```cs\nclass MyService\n{\n    MyService()\n    {\n        // Start subscribing incomming events (call Unregister to unsubscribe events)\n        EventBus.Default.Register(this);\n    }\n\n    [Subscribe]\n    private void SubscribeMethod1(MyEvent myEvent)\n    {\n        // do you stuff here\n    }\n\n    [Subscribe]\n    private void SubscribeMethod2(MyEvent2 myEvent2)\n    {\n        // do you stuff here\n    }\n}\n```\n\n3. Raises an event\n```cs\n// Somewhere else in your project\n\nEventBus.Default.Post(new MyEvent {Data1 = \"my data1\", Data2 = 123});// SubscribeMethod1 will be called\nEventBus.Default.Post(new MyEvent1 {...});// SubscribeMethod2 will be called\n```\n\n### Advance usage\n\nSubscribes an event in background thread\n```cs\n// Currently we're supporting several thread modes:\n//  Post: default mode, invokes subscribers immediately in current caller thread\n//  Thread: invokes subscribers in a new background thread if caller thread is main thread, otherwise invokes subscribers immediately in current caller thread \n//  Async: Always invokes subsribers in a new background thread\n//  Main: Invokes subscribers in main thread (UI thread) in blocking mode\n//  MainOrder: Invokes subsribers in main thread (UI thread) in non-blocking mode\n[Subscribe(ThreadMode = ThreadMode.Thread)]\nprivate void WillBeCalledInBackgroundThread(MyEvent myEvent)\n{\n    // this stuff will be called in background thread\n}\n```\n\nPrevents further propagation of the current event\n```cs\n[Subscribe]\nprivate PostHandled CancelableSubscriber(MyEvent myEvent)\n{\n    // do you stuff here\n    return new PostHandled {Canceled = true};\n}\n```\n\nReturns data for caller (only supports for **Post** or **Main** ThreadMode)\n```cs\n// From subscribers\n[Subscribe]\nprivate PostHandled ReturnValueForCaller1(MyEvent myEvent)\n{\n    // do you stuff here\n    return new PostHandled {Data = \"any data here\"};\n}\n[Subscribe]\nprivate string ReturnValueForCaller2(MyEvent myEvent)\n{\n    // do you stuff here\n    return \"any data here\";\n}\n\n// From caller\nvar results = EventBus.Default.Post(new MyEvent{...});\n```\n\nKeeps the last sticky event of a certain type in memory. Then the sticky event can be delivered to subscribers or queried explicitly.\n```cs\n// Posts a sticky event\nEventBus.Default.Post(new MyEvent{...}, true);// MyEvent likes other events but it's still remaining in memory after called\nEventBus.Default.Post(new MyEvent{...}, true);// this will replace the first event\n\n// Queries a specific sticky event\nvar myStickyEvent = EventBus.Default.GetStickyEvent(typeof(MyEvent));\n\n// Removes a specific sticky event\nEventBus.Default.RemoveStickyEvent(myStickyEvent);\n```\n\nCommunicates with another process uses EventBus\n\nClient process\n```cs\nvar eventbus = ClientEventBus.StartNew(\"my-eventbus\");\neventbus.Post(new MyEvent{...});\n```\n\nMain process (ex: a windows service)\n```cs\nvar eventbus = ServerEventBus.StartNew(\"my-eventbus);\neventbus.Register(this);\n.....\n\n[Subscribe]\nprivate void ListenMyEvent(MyEvent myEvent)\n{\n...\n}\n```\n\n## AppCrash\n\nHandles and reports unhandled-exception automatically\n\n### Basic usage\n\n```cs\nprivate AppCrash appCrash = new AppCrash(); // defines it as a global variable to prevent GC collects this.\n```\nIf an unhandled-exception occurs, a crash report will be shown with notepad, the log file is also saved in **CrashLogs** folder.\n![Crash report](https://2.bp.blogspot.com/-YEUHQsu9YkI/XcqS6PpEJnI/AAAAAAAAZbk/-izm7aMM1ioyT29YNss-FclB8zmYOUCBgCLcBGAsYHQ/s1600/Capture1.PNG)\n\n### Advance usage\n\nShow report with a custom form.\n```cs\n// defines you report form\npublic class YourReportForm : Form, IReportWindow\n{\n    ....\n    public void Initialize(Exception exception, string productName, string devMail)\n    {\n        textBox1.Text = exception.Message;\n        textBox2.Text = productName;\n        textBox3.Text = devMail;\n    }\n}\n\n// Registers you report form\nvar appCrash = new AppCrash\n{\n    ProductName = \"My product\",\n    DeveloperMail = \"xuanson33bk@gmail.com\"\n};\nappCrash.Register(typeof(YourReportForm));\n```\n\nCustom crash logs folder location\n```cs\nvar appCrash = new AppCrash {CrashDir = @\"C:\\newCrashLogsDir\"};\n```\n\n## IoC\n\nInversion of Control Container (bases on [https://github.com/grumpydev/TinyIoC](https://github.com/grumpydev/TinyIoC))\n\n### Basic usage\n\n```cs\n// You can annotate your class by Component, Service, Repository or Controller, they are the same but for easier to understand their roles.\n[Controller]\npublic class MyApp\n{\n    // Properties which are annotated with Autowired attribute will\n    // be injected automatically\n    [Autowired]\n    public MyComponent MyComponent {get; set;}\n\n    // myService will be injected automatically\n    public MyApp(MyService myService) {...}\n    ....\n}\n[Service]\npublic class MyService {....}\n[Component]\npublic class MyComponent{....}\n\n// Somewhere else...\n// App32Context will scan the entry assembly to find classes which are annotated with Controller, Component, Repository or Service attributes.\nusing (var context = new App32Context())\n{\n    var myApp = context.Resolve\u003cMyApp\u003e();\n    myApp.Start();\n}\n```\n\n### Advance usage\n\nScans classes in specific assemblies\n```cs\n// If you don't pass any assembly, the entry assembly will be used\nusing (var context = new App32Context(assembly1, assemly2, assembly3))\n{\n    ....\n}\n```\n\nCreates multiple instances of components\n```cs\n// By default, only one instance of a component will be created (singleton mode). If you want to create a new instance of a component each time it's injected to another component, set Singleton to false\n[Service(Singleton = false)]\npublic class MyService {....}\n```\n\nIConfigLoader, IEventBus and AppCrash are registered automatically so you don't need to worry about these guys\n```cs\nusing (var context = new App32Context())\n{\n    var config = context.Resolve\u003cIConfigLoader\u003e();\n    ....\n}\n```\n\n## DynamicInvoker\n\nManipulates on types which don't need to reference explicitly.\n\n### Basic usage\n\nGets type\n```cs\n// Gets Control class from winform namespace, if current project doesn't reference to winform, return type will be null\nvar type = DynamicInvoker.GetType(\"System.Windows.Forms\", \"Control\");\n```\n\nRegisters event\n```cs\n// Registers ApplicationExit event on Application class\nvar type = DynamicInvoker.GetType(\"System.Windows.Forms\", \"Application\");\nif (type != null)\n{\n    DynamicInvoker.AddEventHandler\u003cEventHandler\u003e(type, \"ApplicationExit\", Application_ApplicationExit);\n}\n```\n\nInvokes method\n```cs\n// Invokes CreateControl method on Control instance\nvar type = DynamicInvoker.GetType(\"System.Windows.Forms\", \"Control\");\nif (type != null)\n{\n    var control = Activator.CreateInstance(type);\n    DynamicInvoker.InvokeMethod(control, \"CreateControl\");\n}\n```\n\nGets or sets property value\n```cs\n// Gets ProductName from Application class\nvar type1 = DynamicInvoker.GetType(\"System.Windows.Forms\", \"Application\");\nif (type1 != null)\n{\n    var productName = DynamicInvoker.GetPropertyValue(type1, \"ProductName\") as string;\n}\n\n// Sets \"My text\" to Text property\nvar type2 = DynamicInvoker.GetType(\"System.Windows.Forms\", \"Control\");\nif (type2 != null)\n{\n    var control = Activator.CreateInstance(type2);\n    DynamicInvoker.SetPropertyValue(control, \"Text\", \"My text\");\n}\n```\n\n## Intercomm\n\nInter process communication which allows processes to communicate each other and synchronize their actions\n\n### Basic usage\n\n1 client - 1 server communication\n\nSender process (client)\n```cs\nusing (var sender = new Sender(\"my-intercomm\"))\n{\n  var response = await sender.SendAsync\u003cstring\u003e(\"Hi server, I'm client1\");\n  Console.WriteLine(\"Response from the other process is: \" + response);\n}\n```\n\nReceiver process (server)\n```cs\nusing (var receiver = new SingleReceiver(\"my-intercomm\"))\n{\n  var request = await receiver.ReceiveAsync\u003cstring\u003e();\n  await receiver.SendAsync(\"Hello \" + request);\n}\n```\n\n### Advantage usage\n\nn client - 1 server communication\n\nSetup your sender process likes the basic usage section.\n\nReceiver process (server)\n```cs\nusing (var receiver = new MultiReceiver(\"my-intercomm\"))\n{\n    await receiver.WaitForSessionAsync(async session =\u003e\n    {\n        var request = await session.ReceiveAsync\u003cstring\u003e();\n        await session.SendAsync(\"Hello \" + request);\n    });\n}\n```\n\nCurrently this library supports communicating through tcp and pipe:\n- Blackcat.Intercomm.Pipe: for pipe supported\n- Blackcat.Intercomm.Tcp: for tcp supported\n","project_url":"https://awesome.ecosyste.ms/api/v1/projects/github.com%2Fsontx%2Fblackcat","html_url":"https://awesome.ecosyste.ms/projects/github.com%2Fsontx%2Fblackcat","lists_url":"https://awesome.ecosyste.ms/api/v1/projects/github.com%2Fsontx%2Fblackcat/lists"}