動手寫一個簡版 asp.net core

来源:https://www.cnblogs.com/weihanli/archive/2020/05/22/mini-aspnetcore.html
-Advertisement-
Play Games

動手寫一個簡版 asp.net core Intro 之前看到過蔣金楠老師的一篇 200 行代碼帶你瞭解 asp.net core 框架,最近參考蔣老師和 Edison 的文章和代碼,結合自己對 asp.net core 的理解 ,最近自己寫了一個 MiniAspNetCore ,寫篇文章總結一下。 ...


動手寫一個簡版 asp.net core

Intro

之前看到過蔣金楠老師的一篇 200 行代碼帶你瞭解 asp.net core 框架,最近參考蔣老師和 Edison 的文章和代碼,結合自己對 asp.net core 的理解 ,最近自己寫了一個 MiniAspNetCore ,寫篇文章總結一下。

HttpContext

HttpContext 可能是最為常用的一個類了,HttpContext 是請求上下文,包含了所有的請求信息以及響應信息,以及一些自定義的用於在不同中間件中傳輸數據的信息

來看一下 HttpContext 的定義:

public class HttpContext
{
    public IServiceProvider RequestServices { get; set; }

    public HttpRequest Request { get; set; }

    public HttpResponse Response { get; set; }

    public IFeatureCollection Features { get; set; }

    public HttpContext(IFeatureCollection featureCollection)
    {
        Features = featureCollection;
        Request = new HttpRequest(featureCollection);
        Response = new HttpResponse(featureCollection);
    }
}

HttpRequest 即為請求信息對象,包含了所有請求相關的信息,

HttpResponse 為響應信息對象,包含了請求對應的響應信息

RequestServices 為 asp.net core 里的RequestServices,代表當前請求的服務提供者,可以使用它來獲取具體的服務實例

Features 為 asp.net core 里引入的對象,可以用來在不同中間件中傳遞信息和用來解耦合

,下麵我們就來看下 HttpRequestHttpResponse 是怎麼實現的

HttpRequest:

public class HttpRequest
{
    private readonly IRequestFeature _requestFeature;

    public HttpRequest(IFeatureCollection featureCollection)
    {
        _requestFeature = featureCollection.Get<IRequestFeature>();
    }

    public Uri Url => _requestFeature.Url;

    public NameValueCollection Headers => _requestFeature.Headers;

    public string Method => _requestFeature.Method;

    public string Host => _requestFeature.Url.Host;

    public Stream Body => _requestFeature.Body;
}

HttpResponse:

public class HttpResponse
{
    private readonly IResponseFeature _responseFeature;

    public HttpResponse(IFeatureCollection featureCollection)
    {
        _responseFeature = featureCollection.Get<IResponseFeature>();
    }

    public bool ResponseStarted => _responseFeature.Body.Length > 0;

    public int StatusCode
    {
        get => _responseFeature.StatusCode;
        set => _responseFeature.StatusCode = value;
    }

    public async Task WriteAsync(byte[] responseBytes)
    {
        if (_responseFeature.StatusCode <= 0)
        {
            _responseFeature.StatusCode = 200;
        }
        if (responseBytes != null && responseBytes.Length > 0)
        {
            await _responseFeature.Body.WriteAsync(responseBytes);
        }
    }
}

Features

上面我們提供我們可以使用 Features 在不同中間件中傳遞信息和解耦合

由上面 HttpRequest/HttpResponse 的代碼我們可以看出來,HttpRequestHttpResponse 其實就是在 IRequestFeatureIResponseFeature 的基礎上封裝了一層,真正的核心其實是 IRequestFeature/IResponseFeature ,而這裡使用介面就很好的實現瞭解耦,可以根據不同的 WebServer 使用不同的 RequestFeature/ResponseFeature,來看下 IRequestFeature/IResponseFeature 的實現

public interface IRequestFeature
{
    Uri Url { get; }

    string Method { get; }

    NameValueCollection Headers { get; }

    Stream Body { get; }
}

public interface IResponseFeature
{
    public int StatusCode { get; set; }

    NameValueCollection Headers { get; set; }

    public Stream Body { get; }
}

這裡的實現和 asp.net core 的實際的實現方式應該不同,asp.net core 里 Headers 同一個 Header 允許有多個值,asp.net core 里是 StringValues 來實現的,這裡簡單處理了,使用了一個 NameValueCollection 對象

上面提到的 Features 是一個 IFeatureCollection 對象,相當於是一系列的 Feature 對象組成的,來看下 FeatureCollection 的定義:

public interface IFeatureCollection : IDictionary<Type, object> { }

public class FeatureCollection : Dictionary<Type, object>, IFeatureCollection
{
}

這裡 IFeatureCollection 直接實現 IDictionary<Type, object> ,通過一個字典 Feature 類型為 Key,Feature 對象為 Value 的字典來保存

為了方便使用,可以定義兩個擴展方法來方便的Get/Set

public static class FeatureExtensions
{
    public static IFeatureCollection Set<TFeature>(this IFeatureCollection featureCollection, TFeature feature)
    {
        featureCollection[typeof(TFeature)] = feature;
        return featureCollection;
    }

    public static TFeature Get<TFeature>(this IFeatureCollection featureCollection)
    {
        var featureType = typeof(TFeature);
        return featureCollection.ContainsKey(featureType) ? (TFeature)featureCollection[featureType] : default(TFeature);
    }
}

Web伺服器

上面我們已經提到了 Web 伺服器通過 IRequestFeature/IResponseFeature 來實現不同 web 伺服器和應用程式的解耦,web 伺服器只需要提供自己的 RequestFeature/ResponseFeature 即可

為了抽象不同的 Web 伺服器,我們需要定義一個 IServer 的抽象介面,定義如下:

public interface IServer
{
    Task StartAsync(Func<HttpContext, Task> requestHandler, CancellationToken cancellationToken = default);
}

IServer 定義了一個 StartAsync 方法,用來啟動 Web伺服器,

StartAsync 方法有兩個參數,一個是 requestHandler,是一個用來處理請求的委托,另一個是取消令牌用來停止 web 伺服器

示例使用了 HttpListener 來實現了一個簡單 Web 伺服器,HttpListenerServer 定義如下:

public class HttpListenerServer : IServer
{
    private readonly HttpListener _listener;
    private readonly IServiceProvider _serviceProvider;

    public HttpListenerServer(IServiceProvider serviceProvider, IConfiguration configuration)
    {
        _listener = new HttpListener();
        var urls = configuration.GetAppSetting("ASPNETCORE_URLS")?.Split(';');
        if (urls != null && urls.Length > 0)
        {
            foreach (var url in urls
                     .Where(u => u.IsNotNullOrEmpty())
                     .Select(u => u.Trim())
                     .Distinct()
                    )
            {
                // Prefixes must end in a forward slash ("/")
                // https://stackoverflow.com/questions/26157475/use-of-httplistener
                _listener.Prefixes.Add(url.EndsWith("/") ? url : $"{url}/");
            }
        }
        else
        {
            _listener.Prefixes.Add("http://localhost:5100/");
        }

        _serviceProvider = serviceProvider;
    }

    public async Task StartAsync(Func<HttpContext, Task> requestHandler, CancellationToken cancellationToken = default)
    {
        _listener.Start();
        if (_listener.IsListening)
        {
            Console.WriteLine("the server is listening on ");
            Console.WriteLine(_listener.Prefixes.StringJoin(","));
        }
        while (!cancellationToken.IsCancellationRequested)
        {
            var listenerContext = await _listener.GetContextAsync();

            var featureCollection = new FeatureCollection();
            featureCollection.Set(listenerContext.GetRequestFeature());
            featureCollection.Set(listenerContext.GetResponseFeature());

            using (var scope = _serviceProvider.CreateScope())
            {
                var httpContext = new HttpContext(featureCollection)
                {
                    RequestServices = scope.ServiceProvider,
                };

                await requestHandler(httpContext);
            }
            listenerContext.Response.Close();
        }
        _listener.Stop();
    }
}

HttpListenerServer 實現的 RequestFeature/ResponseFeatue

public class HttpListenerRequestFeature : IRequestFeature
{
    private readonly HttpListenerRequest _request;

    public HttpListenerRequestFeature(HttpListenerContext listenerContext)
    {
        _request = listenerContext.Request;
    }

    public Uri Url => _request.Url;
    public string Method => _request.HttpMethod;
    public NameValueCollection Headers => _request.Headers;
    public Stream Body => _request.InputStream;
}

public class HttpListenerResponseFeature : IResponseFeature
{
    private readonly HttpListenerResponse _response;

    public HttpListenerResponseFeature(HttpListenerContext httpListenerContext)
    {
        _response = httpListenerContext.Response;
    }

    public int StatusCode { get => _response.StatusCode; set => _response.StatusCode = value; }

    public NameValueCollection Headers
    {
        get => _response.Headers;
        set
        {
            _response.Headers = new WebHeaderCollection();
            foreach (var key in value.AllKeys)
                _response.Headers.Add(key, value[key]);
        }
    }

    public Stream Body => _response.OutputStream;
}

為了方便使用,為 HttpListenerContext 定義了兩個擴展方法,就是上面 HttpListenerServer 中的 GetRequestFeature/GetResponseFeature

public static class HttpListenerContextExtensions
{
    public static IRequestFeature GetRequestFeature(this HttpListenerContext context)
    {
        return new HttpListenerRequestFeature(context);
    }

    public static IResponseFeature GetResponseFeature(this HttpListenerContext context)
    {
        return new HttpListenerResponseFeature(context);
    }
}

RequestDelegate

在上面的 IServer 定義里有一個 requestHandler 的 對象,在 asp.net core 里是一個名稱為 RequestDelegate 的對象,而用來構建這個委托的在 asp.net core 里是 IApplicationBuilder,這些在蔣老師和 Edison 的文章和代碼里都可以看到,這裡我們只是簡單介紹下,我在 MiniAspNetCore 的示例中沒有使用這些對象,而是使用了自己抽象的 PipelineBuilder 和原始委托實現的

asp.net core 里 RequestDelegate 定義:

public delegate Task RequestDelegate(HttpContext context);

其實和我們上面定義用的 Func<HttpContext, Task> 是等價的

IApplicationBuilder 定義:

/// <summary>
/// Defines a class that provides the mechanisms to configure an application's request pipeline.
/// </summary>
public interface IApplicationBuilder
{
    /// <summary>
    /// Gets or sets the <see cref="T:System.IServiceProvider" /> that provides access to the application's service container.
    /// </summary>
    IServiceProvider ApplicationServices { get; set; }

    /// <summary>
    /// Gets the set of HTTP features the application's server provides.
    /// </summary>
    IFeatureCollection ServerFeatures { get; }

    /// <summary>
    /// Gets a key/value collection that can be used to share data between middleware.
    /// </summary>
    IDictionary<string, object> Properties { get; }

    /// <summary>
    /// Adds a middleware delegate to the application's request pipeline.
    /// </summary>
    /// <param name="middleware">The middleware delegate.</param>
    /// <returns>The <see cref="T:Microsoft.AspNetCore.Builder.IApplicationBuilder" />.</returns>
    IApplicationBuilder Use(Func<RequestDelegate, RequestDelegate> middleware);

    /// <summary>
    /// Creates a new <see cref="T:Microsoft.AspNetCore.Builder.IApplicationBuilder" /> that shares the <see cref="P:Microsoft.AspNetCore.Builder.IApplicationBuilder.Properties" /> of this
    /// <see cref="T:Microsoft.AspNetCore.Builder.IApplicationBuilder" />.
    /// </summary>
    /// <returns>The new <see cref="T:Microsoft.AspNetCore.Builder.IApplicationBuilder" />.</returns>
    IApplicationBuilder New();

    /// <summary>
    /// Builds the delegate used by this application to process HTTP requests.
    /// </summary>
    /// <returns>The request handling delegate.</returns>
    RequestDelegate Build();
}

我們這裡沒有定義 IApplicationBuilder,使用了簡化抽象的 IAsyncPipelineBuilder,定義如下:

public interface IAsyncPipelineBuilder<TContext>
{
    IAsyncPipelineBuilder<TContext> Use(Func<Func<TContext, Task>, Func<TContext, Task>> middleware);

    Func<TContext, Task> Build();

    IAsyncPipelineBuilder<TContext> New();
}

對於 asp.net core 的中間件來說 ,上面的 TContext 就是 HttpContext,替換之後也就是下麵這樣的:

public interface IAsyncPipelineBuilder<HttpContext>
{
    IAsyncPipelineBuilder<HttpContext> Use(Func<Func<HttpContext, Task>, Func<HttpContext, Task>> middleware);

    Func<HttpContext, Task> Build();

    IAsyncPipelineBuilder<HttpContext> New();
}

是不是和 IApplicationBuilder 很像,如果不像可以進一步把 Func<HttpContext, Task> 使用 RequestDelegate 替換

public interface IAsyncPipelineBuilder<HttpContext>
{
    IAsyncPipelineBuilder<HttpContext> Use(Func<RequestDelegate, RequestDelegate> middleware);

    RequestDelegate Build();

    IAsyncPipelineBuilder<HttpContext> New();
}

最後再將介面名稱替換一下:

public interface IApplicationBuilder1
{
    IApplicationBuilder1 Use(Func<RequestDelegate, RequestDelegate> middleware);

    RequestDelegate Build();

    IApplicationBuilder1 New();
}

至此,就完全可以看出來了,這 IAsyncPipelineBuilder<HttpContext> 就是一個簡版的 IApplicationBuilder

IAsyncPipelineBuilderIApplicationBuilder 的作用是將註冊的多個中間件構建成一個請求處理的委托

中間件處理流程:

更多關於 PipelineBuilder 構建中間件的信息可以查看 讓 .NET 輕鬆構建中間件模式代碼 瞭解更多

WebHost

通過除了 Web 伺服器之外,還有一個 Web Host 的概念,可以簡單的這樣理解,一個 Web 伺服器上可以有多個 Web Host,就像 IIS/nginx (Web Server) 可以 host 多個站點

可以說 WebHost 離我們的應用更近,所以我們還需要 IHost 來托管應用

public interface IHost
{
    Task RunAsync(CancellationToken cancellationToken = default);
}

WebHost 定義:

public class WebHost : IHost
{
    private readonly Func<HttpContext, Task> _requestDelegate;
    private readonly IServer _server;

    public WebHost(IServiceProvider serviceProvider, Func<HttpContext, Task> requestDelegate)
    {
        _requestDelegate = requestDelegate;
        _server = serviceProvider.GetRequiredService<IServer>();
    }

    public async Task RunAsync(CancellationToken cancellationToken = default)
    {
        await _server.StartAsync(_requestDelegate, cancellationToken).ConfigureAwait(false);
    }
}

為了方便的構建 Host對象,引入了 HostBuilder 來方便的構建一個 Host,定義如下:

public interface IHostBuilder
{
    IHostBuilder ConfigureConfiguration(Action<IConfigurationBuilder> configAction);

    IHostBuilder ConfigureServices(Action<IConfiguration, IServiceCollection> configureAction);

    IHostBuilder Initialize(Action<IConfiguration, IServiceProvider> initAction);

    IHostBuilder ConfigureApplication(Action<IConfiguration, IAsyncPipelineBuilder<HttpContext>> configureAction);

    IHost Build();
}

WebHostBuilder

public class WebHostBuilder : IHostBuilder
{
    private readonly IConfigurationBuilder _configurationBuilder = new ConfigurationBuilder();
    private readonly IServiceCollection _serviceCollection = new ServiceCollection();

    private Action<IConfiguration, IServiceProvider> _initAction = null;

    private readonly IAsyncPipelineBuilder<HttpContext> _requestPipeline = PipelineBuilder.CreateAsync<HttpContext>(context =>
    {
        context.Response.StatusCode = 404;
        return Task.CompletedTask;
    });

    public IHostBuilder ConfigureConfiguration(Action<IConfigurationBuilder> configAction)
    {
        configAction?.Invoke(_configurationBuilder);
        return this;
    }

    public IHostBuilder ConfigureServices(Action<IConfiguration, IServiceCollection> configureAction)
    {
        if (null != configureAction)
        {
            var configuration = _configurationBuilder.Build();
            configureAction.Invoke(configuration, _serviceCollection);
        }

        return this;
    }

    public IHostBuilder ConfigureApplication(Action<IConfiguration, IAsyncPipelineBuilder<HttpContext>> configureAction)
    {
        if (null != configureAction)
        {
            var configuration = _configurationBuilder.Build();
            configureAction.Invoke(configuration, _requestPipeline);
        }
        return this;
    }

    public IHostBuilder Initialize(Action<IConfiguration, IServiceProvider> initAction)
    {
        if (null != initAction)
        {
            _initAction = initAction;
        }

        return this;
    }

    public IHost Build()
    {
        var configuration = _configurationBuilder.Build();
        _serviceCollection.AddSingleton<IConfiguration>(configuration);
        var serviceProvider = _serviceCollection.BuildServiceProvider();

        _initAction?.Invoke(configuration, serviceProvider);

        return new WebHost(serviceProvider, _requestPipeline.Build());
    }

    public static WebHostBuilder CreateDefault(string[] args)
    {
        var webHostBuilder = new WebHostBuilder();
        webHostBuilder
            .ConfigureConfiguration(builder => builder.AddJsonFile("appsettings.json", true, true))
            .UseHttpListenerServer()
            ;

        return webHostBuilder;
    }
}

這裡的示例我在 IHostBuilder 里增加了一個 Initialize 的方法來做一些初始化的操作,我覺得有些數據初始化配置初始化等操作應該在這裡操作,而不應該在 StartupConfigure 方法里處理,這樣 Configure 方法可以更純粹一些,只配置 asp.net core 的請求管道,這純屬個人意見,沒有對錯之分

這裡 Host 的實現和 asp.net core 的實現不同,有需要的可以深究源碼,在 asp.net core 2.x 的版本里是有一個 IWebHost 的,在 asp.net core 3.x 以及 .net 5 里是沒有 IWebHost 的取而代之的是通用主機 IHost, 通過實現了一個 IHostedService 來實現 WebHost

Run

運行示例代碼:

public class Program
{
    private static readonly CancellationTokenSource Cts = new CancellationTokenSource();

    public static async Task Main(string[] args)
    {
        Console.CancelKeyPress += OnExit;

        var host = WebHostBuilder.CreateDefault(args)
            .ConfigureServices((configuration, services) =>
            {
            })
            .ConfigureApplication((configuration, app) =>
            {
                app.When(context => context.Request.Url.PathAndQuery.StartsWith("/favicon.ico"), pipeline => { });

                app.When(context => context.Request.Url.PathAndQuery.Contains("test"),
                    p => { p.Run(context => context.Response.WriteAsync("test")); });
                app
                    .Use(async (context, next) =>
                    {
                        await context.Response.WriteLineAsync($"middleware1, requestPath:{context.Request.Url.AbsolutePath}");
                        await next();
                    })
                    .Use(async (context, next) =>
                    {
                        await context.Response.WriteLineAsync($"middleware2, requestPath:{context.Request.Url.AbsolutePath}");
                        await next();
                    })
                    .Use(async (context, next) =>
                    {
                        await context.Response.WriteLineAsync($"middleware3, requestPath:{context.Request.Url.AbsolutePath}");
                        await next();
                    })
                    ;
                app.Run(context => context.Response.WriteAsync("Hello Mini Asp.Net Core"));
            })
            .Initialize((configuration, services) =>
            {
            })
            .Build();
        await host.RunAsync(Cts.Token);
    }

    private static void OnExit(object sender, EventArgs e)
    {
        Console.WriteLine("exiting ...");
        Cts.Cancel();
    }
}

在示例項目目錄下執行 dotnet run,並訪問 http://localhost:5100/:

仔細觀察瀏覽器 consolenetwork 的話,會發現還有一個請求,瀏覽器會預設請求 /favicon.ico 獲取網站的圖標

因為我們針對這個請求沒有任何中間件的處理,所以直接返回了 404

在訪問 /test,可以看到和剛纔的輸出完全不同,因為這個請求走了另外一個分支,相當於 asp.net core 里 Map/MapWhen 的效果,另外 Run 代表裡中間件的中斷,不會執行後續的中間件

More

上面的實現只是我在嘗試寫一個簡版的 asp.net core 框架時的實現,和 asp.net core 的實現並不完全一樣,如果需要請參考源碼,上面的實現僅供參考,上面實現的源碼可以在 Github 上獲取 https://github.com/WeihanLi/SamplesInPractice/tree/master/MiniAspNetCore

asp.net core 源碼:https://github.com/dotnet/aspnetcore

Reference


您的分享是我們最大的動力!

-Advertisement-
Play Games
更多相關文章
  • 環境 雖說就發郵件這麼個小事,很容易相容Python2, Python3, 但是大家還是擁抱Python3吧, 我這裡沒有做python2的相容寫法,所以需要python3以上。 很多人學習python,不知道從何學起。很多人學習python,掌握了基本語法過後,不知道在哪裡尋找案例上手。很多已經做 ...
  • 我的LeetCode:https://leetcode cn.com/u/ituring/ 我的LeetCode刷題源碼[GitHub]:https://github.com/izhoujie/Algorithmcii LeetCode 面試題55 I. 二叉樹的深度 與以下題目相同 前往:Leet ...
  • 我的LeetCode:https://leetcode cn.com/u/ituring/ 我的LeetCode刷題源碼[GitHub]:https://github.com/izhoujie/Algorithmcii LeetCode 104. 二叉樹的最大深度 題目 給定一個二叉樹,找出其最大深 ...
  • 0. 前言 前言,暫時揮別NHibernate(雖然我突然發現這玩意還挺有意思的,不過看得人不多)。大步進入了有很多小伙伴向我安利的SQLSugar,嗯,我一直叫SugarSQL,好像是這個吧? 這是一個由國內開發者開發的ORM框架,是一個輕量級框架(最新版的sqlSugarCore大概只有290k ...
  • 需求:資料庫數據都是縱向的,呈現的時候要求是橫向(列轉行)同時看到行頭(行轉列)。 分析:很多報表呈現都要求要這樣顯示,用類是可以實現,但是代碼多,又需要建很多dto。發下Excel有轉置功能,但又不想牽扯Excel這一套組件。就使用DataTable來實現,代碼也不多。 先看看示例數據3列10行: ...
  • 同步版本示例: namespace SyncSample { class MyDownloadString { Stopwatch sw = new Stopwatch(); public void DoRun() { const int LargeNumber = 6000000; sw.Star ...
  • 在Linux上搭建基於開源技術的nuget私人保密倉庫 前言 在Linux上搭建nuget私人倉庫一直是一個老大難的問題,主要涉及到以下難點: nuget.org官方使用的Nuget.Server基於.NET Framework的ASP.NET,而不是ASP.NET Core,因此是Windows ...
  • 網上有很多Socket框架,但是我想,C#既然有Socket類,難道不是給人用的嗎? 寫了一個SocketServerHelper和SocketClientHelper,分別隻有5、6百行代碼,比不上大神寫的,和業務代碼耦合也比較重,但對新手非常友好,容易看懂。 支持返回值或回調,支持不定長度的數據 ...
一周排行
    -Advertisement-
    Play Games
  • Timer是什麼 Timer 是一種用於創建定期粒度行為的機制。 與標準的 .NET System.Threading.Timer 類相似,Orleans 的 Timer 允許在一段時間後執行特定的操作,或者在特定的時間間隔內重覆執行操作。 它在分散式系統中具有重要作用,特別是在處理需要周期性執行的 ...
  • 前言 相信很多做WPF開發的小伙伴都遇到過表格類的需求,雖然現有的Grid控制項也能實現,但是使用起來的體驗感並不好,比如要實現一個Excel中的表格效果,估計你能想到的第一個方法就是套Border控制項,用這種方法你需要控制每個Border的邊框,並且在一堆Bordr中找到Grid.Row,Grid. ...
  • .NET C#程式啟動閃退,目錄導致的問題 這是第2次踩這個坑了,很小的編程細節,容易忽略,所以寫個博客,分享給大家。 1.第一次坑:是windows 系統把程式運行成服務,找不到配置文件,原因是以服務運行它的工作目錄是在C:\Windows\System32 2.本次坑:WPF桌面程式通過註冊表設 ...
  • 在分散式系統中,數據的持久化是至關重要的一環。 Orleans 7 引入了強大的持久化功能,使得在分散式環境下管理數據變得更加輕鬆和可靠。 本文將介紹什麼是 Orleans 7 的持久化,如何設置它以及相應的代碼示例。 什麼是 Orleans 7 的持久化? Orleans 7 的持久化是指將 Or ...
  • 前言 .NET Feature Management 是一個用於管理應用程式功能的庫,它可以幫助開發人員在應用程式中輕鬆地添加、移除和管理功能。使用 Feature Management,開發人員可以根據不同用戶、環境或其他條件來動態地控制應用程式中的功能。這使得開發人員可以更靈活地管理應用程式的功 ...
  • 在 WPF 應用程式中,拖放操作是實現用戶交互的重要組成部分。通過拖放操作,用戶可以輕鬆地將數據從一個位置移動到另一個位置,或者將控制項從一個容器移動到另一個容器。然而,WPF 中預設的拖放操作可能並不是那麼好用。為瞭解決這個問題,我們可以自定義一個 Panel 來實現更簡單的拖拽操作。 自定義 Pa ...
  • 在實際使用中,由於涉及到不同編程語言之間互相調用,導致C++ 中的OpenCV與C#中的OpenCvSharp 圖像數據在不同編程語言之間難以有效傳遞。在本文中我們將結合OpenCvSharp源碼實現原理,探究兩種數據之間的通信方式。 ...
  • 一、前言 這是一篇搭建許可權管理系統的系列文章。 隨著網路的發展,信息安全對應任何企業來說都越發的重要,而本系列文章將和大家一起一步一步搭建一個全新的許可權管理系統。 說明:由於搭建一個全新的項目過於繁瑣,所有作者將挑選核心代碼和核心思路進行分享。 二、技術選擇 三、開始設計 1、自主搭建vue前端和. ...
  • Csharper中的表達式樹 這節課來瞭解一下表示式樹是什麼? 在C#中,表達式樹是一種數據結構,它可以表示一些代碼塊,如Lambda表達式或查詢表達式。表達式樹使你能夠查看和操作數據,就像你可以查看和操作代碼一樣。它們通常用於創建動態查詢和解析表達式。 一、認識表達式樹 為什麼要這樣說?它和委托有 ...
  • 在使用Django等框架來操作MySQL時,實際上底層還是通過Python來操作的,首先需要安裝一個驅動程式,在Python3中,驅動程式有多種選擇,比如有pymysql以及mysqlclient等。使用pip命令安裝mysqlclient失敗應如何解決? 安裝的python版本說明 機器同時安裝了 ...