主頁 > .NET開發 > [Abp vNext 原始碼分析] - 13. 本地事件總線與分布式事件總線 (Rabbit MQ)

[Abp vNext 原始碼分析] - 13. 本地事件總線與分布式事件總線 (Rabbit MQ)

2020-09-19 18:51:10 .NET開發

一、簡要介紹

ABP vNext 封裝了兩種事件總線結構,第一種是 ABP vNext 自己實作的本地事件總線,這種事件總線無法跨專案發布和訂閱,第二種則是分布式事件總線,ABP vNext 自己封裝了一個抽象層進行定義,并使用 RabbitMQ 撰寫了一個基本實作,

在使用方式上,兩種事件總線的作用基本相同,

事件總線分布在兩個模塊,在 Volo.Abp.EventBus 模塊內部,定義了事件總線的抽象介面,以及本地事件總線 (ILocalEventBus) 的實作,分布式事件總線的具體實作,是在 Volo.Abp.EventBus.RabbitMQ 模塊內部進行定義,從專案名稱可以看出來,這個模塊是基于 RabbitMQ 訊息佇列實作的,

但是該專案并不是直接參考 RabbitMQ.Client 包,而是在 Volo.Abp.RabbitMQ 專案內部參考,這是因為除了分布式事件總線以外,ABP 還基于 RabbitMQ 實作了一個后臺作業管理器,

ABP vNext 框架便將一些物件抽象出來,放在 Volo.Abp.RabbitMQ 專案內部進行定義和實作,

二、原始碼分析

2.1 事件處理器的注冊

分析原始碼,首先從一個專案的模塊開始,Volo.Abp.EventBus 庫的模塊 AbpEventBusModule 只干了一件事情,在組件注冊的時候,根據組件的實作介面 (ILocalEventHandlerIDistributedEventHandler) 不同,將其賦值給 AbpLocalEventBusOptionsAbpDistributedEventBusOptionsHandlers 屬性,

也就是說,開發人員定義的事件處理程式 (Handler) 都會在依賴注入的時候,都會將其型別 (Type) 添加到事件總線的配置類當中,方便后續進行使用,

2.2 事件總線的介面

通過事件總線模塊的單元測驗我們可以知道,事件的發布與訂閱都是通過 IEventBus 的兩個子介面 (ILocalEventBus/IDistributedEventBus) 進行的,在 IEventBus 介面的定義中,有三種行為,分別是 發布訂閱取消訂閱

對于 ILocalEventBus 介面和 IDistributedEventBus 介面來說,它們都提供了一個,針對本地事件處理器和分布式處理器的特殊訂閱方法,

ILocalEventBus

/// <summary>
/// Defines interface of the event bus.
/// </summary>
public interface ILocalEventBus : IEventBus
{
    /// <summary>
    /// Registers to an event. 
    /// Same (given) instance of the handler is used for all event occurrences.
    /// </summary>
    /// <typeparam name="TEvent">Event type</typeparam>
    /// <param name="handler">Object to handle the event</param>
    IDisposable Subscribe<TEvent>(ILocalEventHandler<TEvent> handler)
        where TEvent : class;
}

IDistributedEventBus

public interface IDistributedEventBus : IEventBus
{
    /// <summary>
    /// Registers to an event. 
    /// Same (given) instance of the handler is used for all event occurrences.
    /// </summary>
    /// <typeparam name="TEvent">Event type</typeparam>
    /// <param name="handler">Object to handle the event</param>
    IDisposable Subscribe<TEvent>(IDistributedEventHandler<TEvent> handler)
        where TEvent : class;
}

2.3 事件總線基本流程和實作

同其他模塊一樣,因為有分布式事件總線和本地事件總線,ABP vNext 同樣抽象了一個 EventBusBase 型別,作為它們的基類實作,

一般的流程,我們是先定義某個事件,然后訂閱該事件并指定事件處理器,最后在某個時刻發布事件,例如下面的代碼:

首先定義了一個事件處理器,專門用于處理 EntityChangedEventData<MyEntity> 事件,

public class MyEventHandler : ILocalEventHandler<EntityChangedEventData<MyEntity>>
{
    public int EntityChangedEventCount { get; set; }

    public Task HandleEventAsync(EntityChangedEventData<MyEntity> eventData)
    {
        EntityChangedEventCount++;
        return Task.CompletedTask;
    }
}
var handler = new MyEventHandler();

LocalEventBus.Subscribe<EntityChangedEventData<MyEntity>>(handler);

await LocalEventBus.PublishAsync(new EntityCreatedEventData<MyEntity>(new MyEntity()));

2.3.1 事件的訂閱

可以看到,這里使用的是 ILocalEventBus 定義的訂閱方法,跳轉到內部實作,它還是呼叫的 EventBus 的方法,

public virtual IDisposable Subscribe<TEvent>(ILocalEventHandler<TEvent> handler) where TEvent : class
{
    // 呼叫基類的 Subscribe 方法,并傳遞 TEvent 的型別,和事件處理器,
    return Subscribe(typeof(TEvent), handler);
}
public virtual IDisposable Subscribe(Type eventType, IEventHandler handler)
{
    return Subscribe(eventType, new SingleInstanceHandlerFactory(handler));
}

可以看到,這里傳遞了一個 SingleInstanceHandlerFactory 物件,這玩意兒是干嘛用的呢?從名字可以看出來,這是一個工廠,是用來創建 Handler (事件處理器) 的工廠,并且是一個單實體的事件處理器工廠,

下面就是 IEventHandlerFactory 介面的定義,以及 SingleInstanceHandlerFactory 實作,

public interface IEventHandlerFactory
{
    // 獲得一個事件處理器包裝物件,即事件處理器執行完畢之后,可以呼叫
    // IEventHandlerDisposeWrapper.Dispose() 進行釋放,
    IEventHandlerDisposeWrapper GetHandler();

    // 判斷在已有的事件處理器工廠集合中,是否已經存在了相同的事件處理器,
    bool IsInFactories(List<IEventHandlerFactory> handlerFactories);
}

public class SingleInstanceHandlerFactory : IEventHandlerFactory
{
    // 構造工廠時,傳遞的事件處理器實體,
    public IEventHandler HandlerInstance { get; }


    public SingleInstanceHandlerFactory(IEventHandler handler)
    {
        HandlerInstance = handler;
    }

    // 通過 EventHandlerDisposeWrapper 包裝事件處理器實體,
    public IEventHandlerDisposeWrapper GetHandler()
    {
        return new EventHandlerDisposeWrapper(HandlerInstance);
    }

    // 判斷針對 HandlerInstance 的事件處理器是否已經存在,
    public bool IsInFactories(List<IEventHandlerFactory> handlerFactories)
    {
        return handlerFactories
            .OfType<SingleInstanceHandlerFactory>()
            .Any(f => f.HandlerInstance == HandlerInstance);
    }
}

針對 IEventHandlerFactory 工廠,還擁有 3 個不同的實作,下表分別說明它們的應用場景,

實作型別 作用
IocEventHandlerFactory 每個工廠對應一個事件處理器的的型別,并通過 ScopeFactory 決議具體的事件處理器,生命周期由 scope 控制,當 scope 釋放時,對應的事件處理器實體也會被銷毀,
SingleInstanceHandlerFactory 每個工廠對應單獨的一個事件處理器實體,事件處理器實體是由創建者控制的,
TransientEventHandlerFactory 每個工廠對應一個事件處理器的型別,區別是它不由 IoC 決議實體,而是使用的 Activator.CreateInstance() 方法構造實體,是一個瞬時物件,呼叫包裝器的 Dispose 即會進行釋放,
TransientEventHandlerFactory<THandler> 每個工廠對應指定的 THandler 事件處理器,生命周期同上面的工廠一樣,

這幾種工廠都是在訂閱操作時,不同的訂閱多載使用不同的工廠,或者是自己指定事件處理器的工廠均可,

public virtual IDisposable Subscribe<TEvent, THandler>()
    where TEvent : class
    where THandler : IEventHandler, new()
{
    return Subscribe(typeof(TEvent), new TransientEventHandlerFactory<THandler>());
}

public virtual IDisposable Subscribe(Type eventType, IEventHandler handler)
{
    return Subscribe(eventType, new SingleInstanceHandlerFactory(handler));
}

不過有一種特殊的行為,開發人員可以 不用顯式訂閱,在 EventBus 型別中,定義了一個 SubscribeHandlers(ITypeList<IEventHandler> handlers) 方法,該方法接收一個型別集合,通過遍歷集合,從事件處理器的定義當中,取得事件處理器監聽的事件型別 TEvent

在取得了事件型別,并知曉了事件處理器型別以后,事件總線就可以訂閱 TEvent 型別的事件,并使用 IocEventHandlerFactory 工廠來構造事件處理器,

protected virtual void SubscribeHandlers(ITypeList<IEventHandler> handlers)
{
    // 遍歷事件處理器的型別,其實這里的就是模塊啟動時,傳遞給 XXXOptions 的集合,
    foreach (var handler in handlers)
    {
        // 獲得事件處理器的所有介面定義,并遍歷介面進行檢查,
        var interfaces = handler.GetInterfaces();
        foreach (var @interface in interfaces)
        {
            // 如果介面沒有實作 IEventHandler 型別,則忽略,
            if (!typeof(IEventHandler).GetTypeInfo().IsAssignableFrom(@interface))
            {
                continue;
            }

            // 從泛型引數當中,獲得定義的事件型別,
            var genericArgs = @interface.GetGenericArguments();
            // 泛型引數完全匹配 1 時,才進行訂閱操作,
            if (genericArgs.Length == 1)
            {
                Subscribe(genericArgs[0], new IocEventHandlerFactory(ServiceScopeFactory, handler));
            }
        }
    }
}

這個訂閱方法在 EventBus 當中是一個抽象方法,分別在本地事件總線和分布式事件總線有實作,這里我們首先講解本地事件的邏輯,

public class LocalEventBus : EventBusBase, ILocalEventBus, ISingletonDependency
{
    protected ConcurrentDictionary<Type, List<IEventHandlerFactory>> HandlerFactories { get; }

    public LocalEventBus(
        IOptions<AbpLocalEventBusOptions> options,
        IServiceScopeFactory serviceScopeFactory)
        : base(serviceScopeFactory)
    {
        Options = options.Value;
        Logger = NullLogger<LocalEventBus>.Instance;

        HandlerFactories = new ConcurrentDictionary<Type, List<IEventHandlerFactory>>();

        // 呼叫父類的方法,將模塊初始化時掃描到的事件處理器,都嘗試進行訂閱,
        SubscribeHandlers(Options.Handlers);
    }

    public override IDisposable Subscribe(Type eventType, IEventHandlerFactory factory)
    {
        GetOrCreateHandlerFactories(eventType)
            // 鎖住集合,以確保執行緒安全,
            .Locking(factories =>
                {
                    // 如果在集合內部,已經有了對應的工廠,則不進行添加,
                    if (!factory.IsInFactories(factories))
                    {
                        factories.Add(factory);
                    }
                }
            );

        // 回傳一個事件處理器工廠注銷器,當呼叫 Dispose() 方法時,會取消之前訂閱的事件,
        return new EventHandlerFactoryUnregistrar(this, eventType, factory);
    }

    private List<IEventHandlerFactory> GetOrCreateHandlerFactories(Type eventType)
    {
        // 根據事件的型別,從字典中獲得該型別的所有事件處理器工廠,
        return HandlerFactories.GetOrAdd(eventType, (type) => new List<IEventHandlerFactory>());
    }
}

上述流程結合 EventBusLocalEventBus 講解了事件的訂閱流程,事件的訂閱操作都是對 HandlerFactories 的操作,往里面添加指定事件的事件處理器工廠,而每個工廠都是跟具體的事件處理器實體/型別進行關聯的,

2.3.2 事件的發布

當開發人員需要發布事件的時候,一般都是通過對應的 EventBus,呼叫回應的 PublishAsync 方法,傳遞要觸發的事件型別與事件資料,介面和基類當中,定義了兩種發布方法的簽名與實作:

public virtual Task PublishAsync<TEvent>(TEvent eventData) where TEvent : class
{
    return PublishAsync(typeof(TEvent), eventData);
}

public abstract Task PublishAsync(Type eventType, object eventData);

第二種方法一共也分為本地事件總線的實作,和分布式事件總線的實作,本地事件比較簡單,我們先分析本地事件總線的實作,

public override async Task PublishAsync(Type eventType, object eventData)
{
    // 定義了一個例外集合,用于接收多個事件處理器執行時,產生的所有例外,
    var exceptions = new List<Exception>();

    // 觸發事件處理器,
    await TriggerHandlersAsync(eventType, eventData, exceptions);

    // 如果有任何例外產生,則拋出到之前的呼叫堆疊,
    if (exceptions.Any())
    {
        if (exceptions.Count == 1)
        {
            exceptions[0].ReThrow();
        }

        throw new AggregateException("More than one error has occurred while triggering the event: " + eventType, exceptions);
    }
}

可以看到真正的觸發行為是在 TriggerHandlersAsync(Type eventType, object eventData, List<Exception> exceptions) 內部進行實作的,

protected virtual async Task TriggerHandlersAsync(Type eventType, object eventData, List<Exception> exceptions)
{
    // 針對于這個的作用,等同于 ConfigureAwait(false) ,
    // 具體可以參考 https://blogs.msdn.microsoft.com/benwilli/2017/02/09/an-alternative-to-configureawaitfalse-everywhere/,
    await new SynchronizationContextRemover();

    // 根據事件的型別,得到它的所有事件處理器工廠,
    foreach (var handlerFactories in GetHandlerFactories(eventType))
    {
        // 遍歷所有的事件處理器工廠,通過 Factory 獲得事件處理器,呼叫 Handler 的 HandleEventAsync 方法,
        foreach (var handlerFactory in handlerFactories.EventHandlerFactories)
        {
            await TriggerHandlerAsync(handlerFactory, handlerFactories.EventType, eventData, exceptions);
        }
    }

    // 如果型別繼承了 IEventDataWithInheritableGenericArgument 介面,那么會檢測泛型引數是否有父類,
    // 如果有父類,則會使用當前的事件資料,為其父類發布一個事件,
    if (eventType.GetTypeInfo().IsGenericType &&
        eventType.GetGenericArguments().Length == 1 &&
        typeof(IEventDataWithInheritableGenericArgument).IsAssignableFrom(eventType))
    {
        var genericArg = eventType.GetGenericArguments()[0];
        var baseArg = genericArg.GetTypeInfo().BaseType;
        if (baseArg != null)
        {
            // 構造基類的事件型別,使用當前一樣的泛型定義,只是泛型引數使用基類,
            var baseEventType = eventType.GetGenericTypeDefinition().MakeGenericType(baseArg);
            // 構建型別的構造引數,
            var constructorArgs = ((IEventDataWithInheritableGenericArgument)eventData).GetConstructorArgs();
            // 通過事件型別和構造引數,構造一個新的事件資料實體,
            var baseEventData = https://www.cnblogs.com/myzony/p/Activator.CreateInstance(baseEventType, constructorArgs);
            // 發布父類的同類事件,
            await PublishAsync(baseEventType, baseEventData);
        }
    }
}

在上述代碼內部,都還沒有真正執行事件處理器,真正的事件處理器執行程式是在下面的方法進行執行的,ABP vNext 通過引入 IEventDataWithInheritableGenericArgument 介面,實作了 型別繼承事件 的觸發,該介面提供了一個 GetConstructorArgs() 方法定義,方便后面生成構造引數,

例如有一個基礎事件叫做 EntityEventData<Student>,如果 Student 繼承自 Person,那么在觸發該事件的時候,也會發布一個 EntityEventData<Person> 事件,

2.3.3 事件處理器的執行

真正事件處理器的執行,是通過下面的方法實作的,大概思路就是通過事件總線工廠,構建了事件處理器的實體,通過反射,呼叫事件處理器的 HandleEventAsync() 方法,如果在處理程序當中,出現了例外,則將例外資料放置在 List<Exception> 集合當中,

protected virtual async Task TriggerHandlerAsync(IEventHandlerFactory asyncHandlerFactory, Type eventType, object eventData, List<Exception> exceptions)
{
    using (var eventHandlerWrapper = asyncHandlerFactory.GetHandler())
    {
        try
        {
            // 獲得事件處理器的型別,
            var handlerType = eventHandlerWrapper.EventHandler.GetType();

            // 判斷事件處理器是本地事件還是分布式事件,
            if (ReflectionHelper.IsAssignableToGenericType(handlerType, typeof(ILocalEventHandler<>)))
            {
                // 獲得方法定義,
                var method = typeof(ILocalEventHandler<>)
                    .MakeGenericType(eventType)
                    .GetMethod(
                        nameof(ILocalEventHandler<object>.HandleEventAsync),
                        new[] { eventType }
                    );

                // 使用工廠創建的實體呼叫方法,
                await (Task)method.Invoke(eventHandlerWrapper.EventHandler, new[] { eventData });
            }
            else if (ReflectionHelper.IsAssignableToGenericType(handlerType, typeof(IDistributedEventHandler<>)))
            {
                var method = typeof(IDistributedEventHandler<>)
                    .MakeGenericType(eventType)
                    .GetMethod(
                        nameof(IDistributedEventHandler<object>.HandleEventAsync),
                        new[] { eventType }
                    );

                await (Task)method.Invoke(eventHandlerWrapper.EventHandler, new[] { eventData });
            }
            else
            {
                // 如果都不是,則說明型別不正確,拋出例外,
                throw new AbpException("The object instance is not an event handler. Object type: " + handlerType.AssemblyQualifiedName);
            }
        }
        // 捕獲到例外都統一添加到例外集合當中,
        catch (TargetInvocationException ex)
        {
            exceptions.Add(ex.InnerException);
        }
        catch (Exception ex)
        {
            exceptions.Add(ex);
        }
    }
}

2.4 分布式事件總線

分布式事件總線的實作都存放在 Volo.Abp.EventBus.RabbitMQ,該專案的代碼比較少,由三個檔案構成,

在 RabbitMQ 模塊的內部,只干了兩件事情,首先從 JSON 組態檔當中,獲取 AbpRabbitMqEventBusOptions 配置的三個引數,然后決議 RabbitMqDistributedEventBus 實體,并呼叫初始化方法 (Initialize()),

[DependsOn(
    typeof(AbpEventBusModule),
    typeof(AbpRabbitMqModule))]
public class AbpEventBusRabbitMqModule : AbpModule
{
    public override void ConfigureServices(ServiceConfigurationContext context)
    {
        var configuration = context.Services.GetConfiguration();

        // 從組態檔讀取配置,
        Configure<AbpRabbitMqEventBusOptions>(configuration.GetSection("RabbitMQ:EventBus"));
    }

    public override void OnApplicationInitialization(ApplicationInitializationContext context)
    {
        // 呼叫初始化方法,
        context
            .ServiceProvider
            .GetRequiredService<RabbitMqDistributedEventBus>()
            .Initialize();
    }
}

2.4.1 分布式事件總線的初始化

public void Initialize()
{
    // 創建一個消費者,并配置交換器和佇列,
    Consumer = MessageConsumerFactory.Create(
        new ExchangeDeclareConfiguration(
            AbpRabbitMqEventBusOptions.ExchangeName,
            type: "direct",
            durable: true
        ),
        new QueueDeclareConfiguration(
            AbpRabbitMqEventBusOptions.ClientName,
            durable: true,
            exclusive: false,
            autoDelete: false
        ),
        AbpRabbitMqEventBusOptions.ConnectionName
    );

    // 消費者在消費訊息的時候,具體的執行邏輯,
    Consumer.OnMessageReceived(ProcessEventAsync);

    // 呼叫基類的方法,自動訂閱對應的事件,
    SubscribeHandlers(AbpDistributedEventBusOptions.Handlers);
}

2.4.2 分布式事件的訂閱

在定義分布式事件的時候,我們必須使用 EventNameAttribute 為事件宣告路由鍵,

public override IDisposable Subscribe(Type eventType, IEventHandlerFactory factory)
{
    var handlerFactories = GetOrCreateHandlerFactories(eventType);

    if (factory.IsInFactories(handlerFactories))
    {
        return NullDisposable.Instance;
    }

    handlerFactories.Add(factory);

    if (handlerFactories.Count == 1) //TODO: Multi-threading!
    {
        // 為消費者系結一個路由鍵,在收到對應的事件時,就會觸發之前系結的方法,
        Consumer.BindAsync(EventNameAttribute.GetNameOrDefault(eventType));
    }

    return new EventHandlerFactoryUnregistrar(this, eventType, factory);
}

訂閱的時候,除了 Consumer.BindAsync() 以外,基本流程和本地事件總線基本一致,

2.4.3 分布式事件的發布

分布式事件總線一樣重寫了發布方法,內部首先使用 IRabbitMqSerializer 序列化器 (基于 JSON.NET) 將事件資料進行序列化,然后將訊息投遞出去,

public override Task PublishAsync(Type eventType, object eventData)
{
    var eventName = EventNameAttribute.GetNameOrDefault(eventType);
    // 序列化事件資料,
    var body = Serializer.Serialize(eventData);

    // 創建一個信道用于通訊,
    using (var channel = ConnectionPool.Get(AbpRabbitMqEventBusOptions.ConnectionName).CreateModel())
    {
        channel.ExchangeDeclare(
            AbpRabbitMqEventBusOptions.ExchangeName,
            "direct",
            durable: true
        );
        
        // 更改投遞模式為持久化模式,
        var properties = channel.CreateBasicProperties();
        properties.DeliveryMode = RabbitMqConsts.DeliveryModes.Persistent;

        // 發布一個新的事件,
        channel.BasicPublish(
            exchange: AbpRabbitMqEventBusOptions.ExchangeName,
            routingKey: eventName,
            mandatory: true,
            basicProperties: properties,
            body: body
        );
    }

    return Task.CompletedTask;
}

2.4.4 分布式事件的執行

執行邏輯都存放在 ProcessEventAsync(IModel channel, BasicDeliverEventArgs ea) 方法內部,基本就是監聽到指定的訊息,首先反序列化訊息,呼叫父類的 TriggerHandlersAsync 去執行具體的事件處理器,

private async Task ProcessEventAsync(IModel channel, BasicDeliverEventArgs ea)
{
    var eventName = ea.RoutingKey;
    var eventType = EventTypes.GetOrDefault(eventName);
    if (eventType == null)
    {
        return;
    }

    var eventData = https://www.cnblogs.com/myzony/p/Serializer.Deserialize(ea.Body, eventType);

    await TriggerHandlersAsync(eventType, eventData);
}

三、總結

ABP vNext 為我們實作了比較完善的本地事件總線,和基于 RabbitMQ 的分布式事件總線,在平時開發程序中,我們本地事件總線的使用頻率應該還是比較高,而分布式事件總線目前仍處于一個半成品,很多高級特性還沒實作,例如重試策略等,所以分布式事件總線要使用的話,建議使用較為成熟的 CAP 庫替代 ABP vNext 的分布式事件總線,

四、其他

360 大病救助 : 在這里向大家求助一下,病人是我親戚,情況屬實,對于他們家庭來說,經濟壓力很大,希望大家能幫助或轉發一下,謝謝大家,

轉載請註明出處,本文鏈接:https://www.uj5u.com/net/83016.html

標籤:.NET Core

上一篇:C# 添加文本、圖片到PDF檔案(基于Spire.Cloud.SDK for .NET)

下一篇:Asp.net Core 3.0 Identity 使用smtp賬戶確認和密碼恢復

標籤雲
其他(157675) Python(38076) JavaScript(25376) Java(17977) C(15215) 區塊鏈(8255) C#(7972) AI(7469) 爪哇(7425) MySQL(7132) html(6777) 基礎類(6313) sql(6102) 熊猫(6058) PHP(5869) 数组(5741) R(5409) Linux(5327) 反应(5209) 腳本語言(PerlPython)(5129) 非技術區(4971) Android(4554) 数据框(4311) css(4259) 节点.js(4032) C語言(3288) json(3245) 列表(3129) 扑(3119) C++語言(3117) 安卓(2998) 打字稿(2995) VBA(2789) Java相關(2746) 疑難問題(2699) 细绳(2522) 單片機工控(2479) iOS(2429) ASP.NET(2402) MongoDB(2323) 麻木的(2285) 正则表达式(2254) 字典(2211) 循环(2198) 迅速(2185) 擅长(2169) 镖(2155) 功能(1967) .NET技术(1958) Web開發(1951) python-3.x(1918) HtmlCss(1915) 弹簧靴(1913) C++(1909) xml(1889) PostgreSQL(1872) .NETCore(1853) 谷歌表格(1846) Unity3D(1843) for循环(1842)

熱門瀏覽
  • WebAPI簡介

    Web體系結構: 有三個核心:資源(resource),URL(統一資源識別符號)和表示 他們的關系是這樣的:一個資源由一個URL進行標識,HTTP客戶端使用URL定位資源,表示是從資源回傳資料,媒體型別是資源回傳的資料格式。 接下來我們說下HTTP. HTTP協議的系統是一種無狀態的方式,使用請求/ ......

    uj5u.com 2020-09-09 22:07:47 more
  • asp.net core 3.1 入口:Program.cs中的Main函式

    本文分析Program.cs 中Main()函式中代碼的運行順序分析asp.net core程式的啟動,重點不是剖析原始碼,而是理清程式開始時執行的順序。到呼叫了哪些實體,哪些法方。asp.net core 3.1 的程式入口在專案Program.cs檔案里,如下。ususing System; us ......

    uj5u.com 2020-09-09 22:07:49 more
  • asp.net網站作為websocket服務端的應用該如何寫

    最近被websocket的一個問題困擾了很久,有一個需求是在web網站中搭建websocket服務。客戶端通過網頁與服務器建立連接,然后服務器根據ip給客戶端網頁發送資訊。 其實,這個需求并不難,只是剛開始對websocket的內容不太了解。上網搜索了一下,有通過asp.net core 實作的、有 ......

    uj5u.com 2020-09-09 22:08:02 more
  • ASP.NET 開源匯入匯出庫Magicodes.IE Docker中使用

    Magicodes.IE在Docker中使用 更新歷史 2019.02.13 【Nuget】版本更新到2.0.2 【匯入】修復單列匯入的Bug,單元測驗“OneColumnImporter_Test”。問題見(https://github.com/dotnetcore/Magicodes.IE/is ......

    uj5u.com 2020-09-09 22:08:05 more
  • 在webform中使用ajax

    如果你用過Asp.net webform, 說明你也算是.NET 開發的老兵了。WEBform應該是2011 2013左右,當時還用visual studio 2005、 visual studio 2008。后來基本都用的是MVC。 如果是新開發的專案,估計沒人會用webform技術。但是有些舊版 ......

    uj5u.com 2020-09-09 22:08:50 more
  • iis添加asp.net網站,訪問提示:由于擴展配置問題而無法提供您請求的

    今天在iis服務器配置asp.net網站,遇到一個問題,記錄一下: 問題:由于擴展配置問題而無法提供您請求的頁面。如果該頁面是腳本,請添加處理程式。如果應下載檔案,請添加 MIME 映射。 WindowServer2012服務器,添加角色安裝完.netframework和iis之后,運行aspx頁面 ......

    uj5u.com 2020-09-09 22:10:00 more
  • WebAPI-處理架構

    帶著問題去思考,大家好! 問題1:HTTP請求和回傳相應的HTTP回應資訊之間發生了什么? 1:首先是最底層,托管層,位于WebAPI和底層HTTP堆疊之間 2:其次是 訊息處理程式管道層,這里比如日志和快取。OWIN的參考是將訊息處理程式管道的一些功能下移到堆疊下端的OWIN中間件了。 3:控制器處理 ......

    uj5u.com 2020-09-09 22:11:13 more
  • 微信門戶開發框架-使用指導說明書

    微信門戶應用管理系統,采用基于 MVC + Bootstrap + Ajax + Enterprise Library的技術路線,界面層采用Boostrap + Metronic組合的前端框架,資料訪問層支持Oracle、SQLServer、MySQL、PostgreSQL等資料庫。框架以MVC5,... ......

    uj5u.com 2020-09-09 22:15:18 more
  • WebAPI-HTTP編程模型

    帶著問題去思考,大家好!它是什么?它包含什么?它能干什么? 訊息 HTTP編程模型的核心就是訊息抽象,表示為:HttPRequestMessage,HttpResponseMessage.用于客戶端和服務端之間交換請求和回應訊息。 HttpMethod類包含了一組靜態屬性: private stat ......

    uj5u.com 2020-09-09 22:15:23 more
  • 部署WebApi隨筆

    一、跨域 NuGet參考Microsoft.AspNet.WebApi.Cors WebApiConfig.cs中配置: // Web API 配置和服務 config.EnableCors(new EnableCorsAttribute("*", "*", "*")); 二、清除默認回傳XML格式 ......

    uj5u.com 2020-09-09 22:15:48 more
最新发布
  • C#多執行緒學習(二) 如何操縱一個執行緒

    <a href="https://www.cnblogs.com/x-zhi/" target="_blank"><img width="48" height="48" class="pfs" src="https://pic.cnblogs.com/face/2943582/20220801082530.png" alt="" /></...

    uj5u.com 2023-04-19 09:17:20 more
  • C#多執行緒學習(二) 如何操縱一個執行緒

    C#多執行緒學習(二) 如何操縱一個執行緒 執行緒學習第一篇:C#多執行緒學習(一) 多執行緒的相關概念 下面我們就動手來創建一個執行緒,使用Thread類創建執行緒時,只需提供執行緒入口即可。(執行緒入口使程式知道該讓這個執行緒干什么事) 在C#中,執行緒入口是通過ThreadStart代理(delegate)來提供的 ......

    uj5u.com 2023-04-19 09:16:49 more
  • 記一次 .NET某醫療器械清洗系統 卡死分析

    <a href="https://www.cnblogs.com/huangxincheng/" target="_blank"><img width="48" height="48" class="pfs" src="https://pic.cnblogs.com/face/214741/20200614104537.png" alt="" /&g...

    uj5u.com 2023-04-18 08:39:04 more
  • 記一次 .NET某醫療器械清洗系統 卡死分析

    一:背景 1. 講故事 前段時間協助訓練營里的一位朋友分析了一個程式卡死的問題,回過頭來看這個案例比較經典,這篇稍微整理一下供后來者少踩坑吧。 二:WinDbg 分析 1. 為什么會卡死 因為是表單程式,理所當然就是看主執行緒此時正在做什么? 可以用 ~0s ; k 看一下便知。 0:000> k # ......

    uj5u.com 2023-04-18 08:33:10 more
  • SignalR, No Connection with that ID,IIS

    <a href="https://www.cnblogs.com/smartstar/" target="_blank"><img width="48" height="48" class="pfs" src="https://pic.cnblogs.com/face/u36196.jpg" alt="" /></a>...

    uj5u.com 2023-03-30 17:21:52 more
  • 一次對pool的誤用導致的.net頻繁gc的診斷分析

    <a href="https://www.cnblogs.com/dotnet-diagnostic/" target="_blank"><img width="48" height="48" class="pfs" src="https://pic.cnblogs.com/face/3115652/20230225090434.png" alt=""...

    uj5u.com 2023-03-28 10:15:33 more
  • 一次對pool的誤用導致的.net頻繁gc的診斷分析

    <a href="https://www.cnblogs.com/dotnet-diagnostic/" target="_blank"><img width="48" height="48" class="pfs" src="https://pic.cnblogs.com/face/3115652/20230225090434.png" alt=""...

    uj5u.com 2023-03-28 10:13:31 more
  • C#遍歷指定檔案夾中所有檔案的3種方法

    <a href="https://www.cnblogs.com/xbhp/" target="_blank"><img width="48" height="48" class="pfs" src="https://pic.cnblogs.com/face/957602/20230310105611.png" alt="" /></a&...

    uj5u.com 2023-03-27 14:46:55 more
  • C#/VB.NET:如何將PDF轉為PDF/A

    <a href="https://www.cnblogs.com/Carina-baby/" target="_blank"><img width="48" height="48" class="pfs" src="https://pic.cnblogs.com/face/2859233/20220427162558.png" alt="" />...

    uj5u.com 2023-03-27 14:46:35 more
  • 武裝你的WEBAPI-OData聚合查詢

    <a href="https://www.cnblogs.com/podolski/" target="_blank"><img width="48" height="48" class="pfs" src="https://pic.cnblogs.com/face/616093/20140323000327.png" alt="" /><...

    uj5u.com 2023-03-27 14:46:16 more