您现在的位置是:网站首页> .NET Core

.NET Core 基础知识

摘要

.NET Core基础知识

点击下载英文DotNET框架点击下载中文DotNET框架

C#/.NET/.NET Core学习、工作、面试指南

.Net Core的优点

使用vs2022 打包项目

.net例子代码

Dapper入门

Entity Framework Core (EFCore)学习

ServerPUSH的使用

跨平台的DLL

ObservableCollection<T> 类 表示一个动态数据集合




使用vs2022 打包项目

点击查看原文

步骤1:在线载安装Setup project

1.png

2.png



 双击下载,然后关闭vs,自动安装,安装完成后提示。VSIX Installer修改安装完成后,关闭vs,再次打开vs。                                                                                                    

步骤2:打开你想要打包的项目,点解决方案右击,添加新建项目。    

3.png                                       

步骤3:选setup project后点下一步,取项目名后,点击创建。

1.png

2.png


步骤4:之后进入vs的主界面,有项目和打包选项,观察图如下:

1.png


步骤5:添加打包项目资料:

1.找出项目的资料,将项目的资源文件夹路径复制,记好路径

 

2.png

1.png

 


 右击添加application  folder,选add,再选folder,创建一个跟之前一样命名的文件夹src

 1.png


 2.创建完后,在src右击进一步选择添加项目src里面的文件资源,之后全选添加项目src里面的全部资源。

2.png


3. 添加,右边name会出现src的资源,src没有的资源在下面步骤讲。

3.png

 4.添加完项目资源后,点击application folder,将我选中的文件在name哪里直接粘贴就可,没有文件的可以去我资源哪里拿,主要是怕有些电脑没有这些.dill文件,打包后安装不了。

1.png

 5.下一步进行项目输出,按下面图片步骤来。

 2.png

3.png


6.之后进行的是,安装后自动添加快捷方式的步骤:

 1.png

2.png


 


 


 步骤6.桌面图标更改

1.下面是桌面快捷方式图标的更改,如果你不想这步可以省略。

1.png

2.png


 


 2.这个icon文件,你可以弄好像上面步骤一样依次放好在项目文件夹和打包文件夹src中。之后按上图进行游览打开!注:icon图标的制作你可以在下面这个网站制作!找好图片进行转换就可了制作ico图标 | 在线ico图标转换工具 方便制作favicon.ico - 比特虫 - Bitbug.net


3.png

4.png

5.png

6.png





 


 步骤7.重新生成,打包文件:

1.按上面步骤依次全部弄好后,可以进行打包了!

1.png

2.png


 


 2.之后看输出全部生成成功即可!

 点击打包按右击,找到打包的文件,点进Debug

3.png

4.png


 


3.找到.msi文件就可以进行安装了,也可以将文件发给别人,别人也可以在没有配置环境的情况下进行安装!注:也可以点击试着自己可不可以安装成功先,然后再吧.msi文件发给别人!


1.png

2.png

 


 


.Net Core的优点

  .Net Core的与.Net Framework对比,具有以下几个优势。

  跨平台。可以在window、Linux、macOS平台上运行;跨平台这一点一直是.Net Framework的痛点,尤其是在被java等各种语言抢占了市场后,.Net就被抨击不能在Linux平台上运行。

  性能强化;.Net Core做了性能优化,在各方面的测试中,都能体现出性能的优化。

  代码开源;如今的.Net Core源代码开源,这又是一改以往.Net给人不开源的封闭印象。

  运行自托管;既然是跨平台了,部署方法也不再依赖iis,通过装sdk,然后可以直接运行部署好的程序。

  具体的优点,官网上的介绍,https://docs.microsoft.com/zh-cn/aspnet/core/?view=aspnetcore-2.2

ASP.NET Core 具有如下优点:

  .Net Core是做出了改变,在配置上和.Net Framework肯定有区别。但主要使用的框架,也大为接近,像mvc、webapi框架一样都有,.Net Core在开发新的api时,使用方式也倾向于.Net Framework的方式。可以说,Framework开发者在初次使用Core会有些门槛,但上手起来还是挺快的。



1.IOC(转:https://www.cnblogs.com/artech/p/inside-asp-net-core.html)


IoC的全名Inverse of Control,翻译成中文就是“控制反转”或者“控制倒置”。控制反转也好,控制倒置也罢,它体现的意思是控制权的转移,即原来控制权在A手中,现在需要B来接管。那么具体对于软件设计来说,IoC所谓的控制权的转移具有怎样的体现呢?要回答这个问题,就需要先了解IoC的C(Control)究竟指的是怎样一种控制。对于我们所在的任何一件事,不论其大小,其实可以分解成相应的步骤,所以任何一件事都有其固有的流程,IoC涉及的所谓控制可以理解为“针对流程的控制


我们通过一个具体事例来说明传统的设计在采用了IoC之后针对流程的控制是如何实现反转的。比如说现在设计一个针对Web的MVC类库,我们不妨将其命名为MvcLib。简单起见,这个类库中只包含如下这个同名的静态类


public static class MvcLib

{

    public static Task ListenAsync(Uri address);

    public static Task<Request> ReceiveAsync();

    public static Task<Controller> CreateControllerAsync(Request request);

    public static Task<View> ExecuteControllerAsync(Controller controller);

    public static Task RenderViewAsync(View view);

}

MvcLib提供了如上5个方法帮助我们完成整个HTTP请求流程中的5个核心任务。具体来说,ListenAsync方法启动一个监听器并将其绑定到指定的地址进行HTTP请求的监听,抵达的请求通过ReceiveAsync方法进行接收,我们将接收到的请求通过一个Request对象来表示。CreateControllerAsync方法根据接收到的请求解析并激活请求的目标Controller对象。ExecuteControllerAsync方法执行激活的Controller并返回一个表示视图的View对象。RenderViewAsync最终将View对象转换成HTML并作为当前请求响应的内容返回给请求的客户端。


现在我们在这个MvcLib的基础上创建一个真正的MVC应用,那么除了按照MvcLib的规范自定义具体的Controller和View之外,我们还需要自行控制从请求的监听与接收、Controller的激活与执行以及View的最终呈现在内的整个流程,这样一个执行流程反映在如下所示的代码中。


class Program

{

    static async Task Main()

    {

        while (true)

        {

            Uri address = new Uri("http://0.0.0.0:8080/mvcapp");

            await MvcLib.ListenAsync(address);

            while (true)

            {

                var request = await MvcLib.ReceiveAsync();

                var controller = await MvcLib.CreateControllerAsync(request);

                var view = await MvcLib.ExecuteControllerAsync(controller);

                await MvcLib.RenderViewAsync(view);

            }

        }

    }

}

2.好莱坞法则


在好莱坞,把简历递交给演艺公司后就只有回家等待。由演艺公司对整个娱乐项目的完全控制,演员只能被动式的接受电影公司的工作,在需要的环节中,完成自己的演出。“不要给我们打电话,我们会给你打电话(don‘t call us, we‘ll call you)”这是著名的好莱坞法则( Hollywood Principle或者 Hollywood Low),IoC完美地体现了这一法则。


在IoC的应用语境中,框架就像是掌握整个电影制片流程的电影公司,由于它是整个工作流程的实际控制者,所以只有它知道哪个环节需要哪些组件。应用程序就像是演员,它只需要按照框架定制的规则注册这些组件就可以了,因为框架会在适当的时机字典加载并执行注册的组件。


以熟悉的ASP.NET Core MVC或者ASP.NET MVC应用开发来说,我们只需要按照约定规则(比如目录结构和命名等)定义相应的Controller类型和View文件就可以了。当ASP.NET (Core )MVC框架在进行处理请求的过程中,它会根据解析生成的路由参数定义为对应的Controller类型,并按照预定义的规则找到我们定义的Controller,然后自动创建并执行它。如果定义在当前Action方法需要呈现一个View,框架自身会根据预定义的目录约定找到我们定义的View文件,并对它实施动态编译和执行。整个流程处处体现了“框架Call应用”的好莱坞法则。


总的来说,我们在一个框架的基础上进行应用开发,就相当于在一条调试好的流水线上生成某种商品,我们只需要在相应的环节准备对应的原材料,最终下线的就是我们希望得到的最终产品。IoC几乎是所有框架均具有的一个固有属性,从这个意义上讲,“IoC框架”的说法其实是错误的,世界上并没有什么IoC框架,或者说几乎所有的框架都是IoC框架。


3.依赖注入(DI容器)


IoC主要体现了这样一种设计思想:通过将一组通用流程的控制权从应用转移到框架中以实现对流程的复用,并按照“好莱坞法则”实现应用程序的代码与框架之间的交互。我们可以采用若干设计模式以不同的方式实现IoC


DI是一种“对象提供型”的设计模式,在这里我们将提供的对象统称为“服务”、“服务对象”或者“服务实例”。在一个采用DI的应用中,在定义某个服务类型的时候,我们直接将依赖的服务采用相应的方式注入进来。按照“面向接口编程”的原则,被注入的最好是依赖服务的接口而非实现。


在应用启动的时候,我们会对所需的服务进行全局注册。服务一般都是针对接口进行注册的,服务注册信息的核心目的是为了在后续消费过程中能够根据接口创建或者提供对应的服务实例。按照“好莱坞法则”,应用只需要定义好所需的服务,服务实例的激活和调用则完全交给框架来完成,而框架则会采用一个独立的“容器(Container)”来提供所需的每一个服务实例。


我们将这个被框架用来提供服务的容器称为“DI容器”,也由很多人将其称为“IoC容器”,根据我们在《控制反转》针对IoC的介绍,我不认为后者是一个合理的称谓。DI容器之所以能够按照我们希望的方式来提供所需的服务是因为该容器是根据服务注册信息来创建的,服务注册了包含提供所需服务实例的所有信息。


以Autofac框架为列 


框架特性


1,灵活的组件实例化:Autofac支持自动装配,给定的组件类型Autofac自动选择使用构造函数注入或者属性注入,Autofac还可以基于lambda表达式创建实例,这使得容器非常灵活,很容易和其他的组件集成。

2,资源管理的可视性:基于依赖注入容器构建的应用程序的动态性,意味着什么时候应该处理那些资源有点困难。Autofac通过容器来跟踪组件的资源管理。对于不需要清理的对象,例如Console.Out,我们调用ExternallyOwned()方法告诉容器不用清理。细粒度的组件生命周期管理:应用程序中通常可以存在一个应用程序范围的容器实例,在应用程序中还存在大量的一个请求的范围的对象,例如一个HTTP请求,一个IIS工作者线程或者用户的会话结束时结束。通过嵌套的容器实例和对象的作用域使得资源的可视化。

3,Autofac的设计上非常务实,这方面更多是为我们这些容器的使用者考虑:

●组件侵入性为零:组件不需要去引用Autofac。

●灵活的模块化系统:通过模块化组织你的程序,应用程序不用纠缠于复杂的XML配置系统或者是配置参数。

●自动装配:可以是用lambda表达式注册你的组件,autofac会根据需要选择构造函数或者属性注入

●XML配置文件的支持:XML配置文件过度使用时很丑陋,但是在发布的时候通常非常有用


(1).属性注入


builder.Register(c => new A { B = c.Resolve<B>() });

为了支持循环依赖,使用激活的事件处理程序:


builder.Register(c => new A()).OnActivated(e => e.Instance.B = e.Context.Resolve<B>());

如果是一个反射组件,使用PropertiesAutowired()修改注册属性:


builder.RegisterType<A>().PropertiesAutowired();

如果你有一个特定的属性和值需要连接,你可以使用WithProperty()修改:


builder.RegisterType<A>().WithProperty("PropertyName", propertyValue);

(2).方法注入


调用一个方法来设置一个组件的值的最简单的方法是,使用一个lambda表达式组件和正确的调用激活处理方法。


builder.Register(c => {

  var result = new MyObjectType();

  var dep = c.Resolve<TheDependency>();

  result.SetTheDependency(dep);

  return result;

});

如果你不能使用一个lambda表达式注册,你可以添加一个激活事件处理程序(activating event handler)


builder

  .Register<MyObjectType>()

  .OnActivating(e => {

    var dep = e.Context.Resolve<TheDependency>();

    e.Instance.SetTheDependency(dep);

  });

(3).构造函数注入


    // 创建你的builder

    var builder = new ContainerBuilder();


    // 通常你只关心这个接口的一个实现

    builder.RegisterType<SomeType>().As<IService>();


    // 当然,如果你想要全部的服务(不常用),可以这么写:

    builder.RegisterType<SomeType>().AsSelf().As<IService>();

 生命周期


AutoFac中的生命周期概念非常重要,AutoFac也提供了强大的生命周期管理的能力。

AutoFac定义了三种生命周期:


Per Dependency

Single Instance

Per Lifetime Scope

Per Dependency为默认的生命周期,也被称为’transient’或’factory’,其实就是每次请求都创建一个新的对象


[Fact]

    public void per_dependency()

    {

        var builder = new ContainerBuilder();

        builder.RegisterType<MyClass>().InstancePerDependency();

        IContainer container = builder.Build();

        var myClass1 = container.Resolve<MyClass>();

        var myClass2 = container.Resolve<MyClass>();

        Assert.NotEqual(myClass1,myClass2);

    }

Single Instance也很好理解,就是每次都用同一个对象


 


[Fact]

    public void single_instance()

    {

        var builder = new ContainerBuilder();

        builder.RegisterType<MyClass>().SingleInstance();

      

        IContainer container = builder.Build();

        var myClass1 = container.Resolve<MyClass>();

        var myClass2 = container.Resolve<MyClass>();

      

        Assert.Equal(myClass1,myClass2);

    }

Per Lifetime Scope,同一个Lifetime生成的对象是同一个实例


 


[Fact]

    public void per_lifetime_scope()

    {

        var builder = new ContainerBuilder();

        builder.RegisterType<MyClass>().InstancePerLifetimeScope();

      

        IContainer container = builder.Build();

        var myClass1 = container.Resolve<MyClass>();

        var myClass2 = container.Resolve<MyClass>();

      

        ILifetimeScope inner = container.BeginLifetimeScope();

        var myClass3 = inner.Resolve<MyClass>();

        var myClass4 = inner.Resolve<MyClass>();

      

        Assert.Equal(myClass1,myClass2);

        Assert.NotEqual(myClass2,myClass3);

        Assert.Equal(myClass3,myClass4);

    }

[Fact]

    public void life_time_and_dispose()

    {

        var builder = new ContainerBuilder();

        builder.RegisterType<Disposable>();


        using (IContainer container = builder.Build())

        {

            var outInstance = container.Resolve<Disposable>(new NamedParameter("name", "out"));


            using(var inner = container.BeginLifetimeScope())

            {

                var inInstance = container.Resolve<Disposable>(new NamedParameter("name", "in"));

            }//inInstance dispose here

        }//out dispose here

    }

 4.过滤器(转:https://www.cnblogs.com/niklai/p/5676632.html)


下图展示了Asp.Net Core MVC框架默认实现的过滤器的执行顺序:




Authorization Filters:身份验证过滤器,处在整个过滤器通道的最顶层。对应的类型为: AuthorizeAttribute.cs 对应的接口有同步和异步两个版本: IAuthorizationFilter.cs 、 IAsyncAuthorizationFilter.cs


Resource Filters:资源过滤器。因为所有的请求和响应都将经过这个过滤器,所以在这一层可以实现类似缓存的功能。对应的接口有同步和异步两个版本: IResourceFilter.cs 、 IAsyncResourceFilter.cs


Action Filters:方法过滤器。在控制器的Action方法执行之前和之后被调用,一个很常用的过滤器。对应的接口有同步和异步两个版本: IActionFilter.cs 、 IAsyncActionFilter.cs


Exception Filters:异常过滤器。当Action方法执行过程中出现了未处理的异常,将会进入这个过滤器进行统一处理,也是一个很常用的过滤器。对应的接口有同步和异步两个版本: IExceptionFilter.cs 、 IAsyncExceptionFilter.cs


Result Filters:返回值过滤器。当Action方法执行完成的结果在组装或者序列化前后被调用。对应的接口有同步和异步两个版本: IResultFilter.cs 、 IAsyncResultFilter.cs


5.中间件(转https://www.cnblogs.com/niklai/p/5665272.html)


Asp.Net Core,管道模型流程图




IHttpModule和IHttpHandler不复存在,取而代之的是一个个中间件(Middleware)。


Server将接收到的请求直接向后传递,依次经过每一个中间件进行处理,然后由最后一个中间件处理并生成响应内容后回传,再反向依次经过每个中间件,直到由Server发送出去。


中间件就像一层一层的“滤网”,过滤所有的请求和相应。这一设计非常适用于“请求-响应”这样的场景——消息从管道头流入最后反向流出。


接下来将演示在Asp.Net Core里如何实现中间件功能。


 


IHttpModule和IHttpHandler不复存在,取而代之的是一个个中间件(Middleware)。


Server将接收到的请求直接向后传递,依次经过每一个中间件进行处理,然后由最后一个中间件处理并生成响应内容后回传,再反向依次经过每个中间件,直到由Server发送出去。


中间件就像一层一层的“滤网”,过滤所有的请求和相应。这一设计非常适用于“请求-响应”这样的场景——消息从管道头流入最后反向流出。


接下来将演示在Asp.Net Core里如何实现中间件功能。


Middleware


 Middleware支持Run、Use和Map三种方法进行注册,下面将展示每一种方法的使用方式。


 Run方法


所有需要实现的自定义管道都要在 Startup.cs 的 Configure 方法里添加注册。


public void Configure(IApplicationBuilder app, ILoggerFactory loggerFactory)

        {

            // 添加日志支持

            loggerFactory.AddConsole();

            loggerFactory.AddDebug();

            

            // 添加NLog日志支持

            loggerFactory.AddNLog();


            // 添加自定义中间件

            app.Run(async context =>

            {

                await context.Response.WriteAsync("Hello World!");

            });


            // 添加MVC中间件

            //app.UseMvc();

        }

启动调试,访问地址 http://localhost:5000/ ,页面显示Hello World!字样


再次添加一个Run方法


public void Configure(IApplicationBuilder app, ILoggerFactory loggerFactory)

        {

            // 添加日志支持

            loggerFactory.AddConsole();

            loggerFactory.AddDebug();

            

            // 添加NLog日志支持

            loggerFactory.AddNLog();


            // 添加自定义中间件

            app.Run(async context =>

            {

                await context.Response.WriteAsync("Hello World!");

            });


            app.Run(async context =>

            {

                await context.Response.WriteAsync("Hello World too!");

            });


            // 添加MVC中间件

            //app.UseMvc();

        }

启动调试,再次访问发现页面上只有Hello World!字样。


原因是:Run的这种用法表示注册的此中间件为管道内的最后一个中间件,由它处理完请求后直接返回。


Use方法 


public void Configure(IApplicationBuilder app, ILoggerFactory loggerFactory)

        {

            // 添加日志支持

            loggerFactory.AddConsole();

            loggerFactory.AddDebug();

            

            // 添加NLog日志支持

            loggerFactory.AddNLog();


            // 添加自定义中间件

            app.Use(async (context, next) =>

            {

                await context.Response.WriteAsync("Hello World!");

            });


            // 添加MVC中间件

            //app.UseMvc();

        }

启动调试,访问页面同样显示Hello World!字样。我们发现使用Use方法替代Run方法,一样可以实现同样的功能。


再次添加一个Use方法,将原来的Use方法内容稍作调整,尝试实现页面显示两个Hello World!字样。


public void Configure(IApplicationBuilder app, ILoggerFactory loggerFactory)

        {

            // 添加日志支持

            loggerFactory.AddConsole();

            loggerFactory.AddDebug();

            

            // 添加NLog日志支持

            loggerFactory.AddNLog();


            // 添加自定义中间件

            app.Use(async (context, next) =>

            {

                await context.Response.WriteAsync("Hello World!");

                await next();

            });


            app.Use(async (context, next) =>

            {

                await context.Response.WriteAsync("Hello World too!");

            });


            // 添加MVC中间件

            //app.UseMvc();

        }

将两个Use方法换个顺序,稍微调整一下内容,再次启动调试,访问页面,发现字样输出顺序也发生了变化。


public void Configure(IApplicationBuilder app, ILoggerFactory loggerFactory)

        {

            // 添加日志支持

            loggerFactory.AddConsole();

            loggerFactory.AddDebug();

            

            // 添加NLog日志支持

            loggerFactory.AddNLog(); HelloworldMiddleware.cs 


            // 添加自定义中间件

            app.Use(async (context, next) =>

            {

                await context.Response.WriteAsync("Hello World too!");

                await next();

            });


            app.Use(async (context, next) =>

            {

                await context.Response.WriteAsync("Hello World!");

            });


            // 添加MVC中间件

            //app.UseMvc();

        }

从上面的例子可以发现,通过Use方法注册的中间件,如果不调用next方法,效果等同于Run方法。当调用next方法后,此中间件处理完后将请求传递下去,由后续的中间件继续处理。


当注册中间件顺序不一样时,处理的顺序也不一样,这一点很重要,当注册的自定义中间件数量较多时,需要考虑哪些中间件先处理请求,哪些中间件后处理请求。


另外,我们可以将中间件单独写成独立的类,通过UseMiddleware方法同样可以完成注册。下面将通过独立的中间件类重写上面的演示功能。


新建两个中间件类: HelloworldMiddleware.cs 、 HelloworldTooMiddleware.cs


using System.Threading.Tasks;

using Microsoft.AspNetCore.Http;


namespace WebApiFrame.Core.Middlewares

{

    public class HelloworldMiddleware

    {

        private readonly RequestDelegate _next;


        public HelloworldMiddleware(RequestDelegate next){

            _next = next;

        }


        public async Task Invoke(HttpContext context){

            await context.Response.WriteAsync("Hello World!");

            await _next(context);

        }

    }

}


HelloworldMiddleware.cs

using System.Threading.Tasks;

using Microsoft.AspNetCore.Http;


namespace WebApiFrame.Core.Middlewares

{

    public class HelloworldTooMiddleware

    {

        private readonly RequestDelegate _next;


        public HelloworldTooMiddleware(RequestDelegate next){

            _next = next;

        }


        public async Task Invoke(HttpContext context){

            await context.Response.WriteAsync("Hello World too!");

        }

    }

}


HelloworldTooMiddleware.cs

修改 Startup.cs 的Configure方法内容


public void Configure(IApplicationBuilder app, ILoggerFactory loggerFactory)

        {

            // 添加日志支持

            loggerFactory.AddConsole();

            loggerFactory.AddDebug();

            

            // 添加NLog日志支持

            loggerFactory.AddNLog();


            // 添加自定义中间件

            app.UseMiddleware<HelloworldMiddleware>();

            app.UseMiddleware<HelloworldTooMiddleware>();


            // 添加MVC中间件

            //app.UseMvc();

        }

启动调试,访问页面,可以看到同样的效果。


Map方法


Map方法主要通过请求路径和其他自定义条件过滤来指定注册的中间件,看起来更像一个路由。


修改 Startup.cs 的Configure方法内容,增加静态方法MapTest


public void Configure(IApplicationBuilder app, ILoggerFactory loggerFactory)

        {

            // 添加日志支持

            loggerFactory.AddConsole();

            loggerFactory.AddDebug();

            

            // 添加NLog日志支持

            loggerFactory.AddNLog();


            // 添加自定义中间件

            app.Map("/test", MapTest);


            // 添加MVC中间件

            //app.UseMvc();

        }


        private static void MapTest(IApplicationBuilder app){

            app.Run(async context => {

                await context.Response.WriteAsync("Url is " + context.Request.PathBase.ToString());

            });

        }

启动调试,访问路径 http://localhost:5000/test ,页面显示如下内容


 


但是访问其他路径时,页面没有内容显示。从这个可以看到,Map方法通过类似路由的机制,将特定的Url地址请求引导到固定的方法里,由特定的中间件处理。


另外,Map方法还可以实现多级Url“路由”,其实就是Map方法的嵌套使用


// 添加自定义中间件

            app.Map("/level1", lv1App => {

                app.Map("/level1.1", lv11App => {

                    // /level1/level1.1


                });

                

                app.Map("/level1.2", lv12App => {

                    // /level1/level1.2


                });

            });

也可以通过MapWhen方法使用自定义条件进行“路由”


public void Configure(IApplicationBuilder app, ILoggerFactory loggerFactory)

        {

            // 添加日志支持

            loggerFactory.AddConsole();

            loggerFactory.AddDebug();

            

            // 添加NLog日志支持

            loggerFactory.AddNLog();


            // 添加自定义中间件

            app.MapWhen(context =>

            {

                return context.Request.Query.ContainsKey("a");

            }, MapTest);


            // 添加MVC中间件

            //app.UseMvc();

        }


        private static void MapTest(IApplicationBuilder app)

        {

            app.Run(async context =>

            {

                await context.Response.WriteAsync($"Url is {context.Request.Path.ToString()}{context.Request.QueryString.Value}");

            });


        }

启动调试,访问路径 http://localhost:5000/path?a=1&b=2 ,页面显示如下内容




只有当请求参数中含有a时,页面才正常显示内容。


其他内置的中间件


Asp.Net Core框架内置了几个中间件




 6.路由(转https://www.cnblogs.com/tianma3798/p/6920638.html)


1.API定义


/*

    *  API 定义如下

    *  GET     api/menu 获取菜单列表

    *  POST    api/menu 添加模块

    *  PUT     api/menu 修改模块

    *  PATCH   api/menu 修改菜单信息

    *  DELETE  api/menu 删除模块

    */

2.后台代码 :


MenuModelContext _Context = new MenuModelContext();

/// <summary>

/// 获取列表

/// </summary>

/// <returns></returns>

[HttpGet]

public IEnumerable<Menu> Get()

{

    List<Menu> list = _Context.Menu.ToList();

    return list;

}

/// <summary>

/// 添加模块对象

/// </summary>

/// <param name="m"></param>

/// <returns></returns>

[HttpPost]

public bool Add(Model m)

{

    try

    {

        m.AddTime = DateTime.Now;

        _Context.Model.Add(m);

        _Context.SaveChanges();

        return true;

    }

    catch (Exception ex)

    {

        return false;

    }

}

/// <summary>

/// 修改模块

/// </summary>

/// <param name="m"></param>

/// <returns></returns>

[HttpPut]

public bool Update(Model m)

{

    try

    {

        Model oldModel = _Context.Model.Find(m.ModelId);

        oldModel.ModelName = m.ModelName;

        oldModel.SortNumber = m.SortNumber;

        _Context.SaveChanges();

        return true;

    }

    catch (Exception ex)

    {

        return false;

    }

}

/// <summary>

/// 修改菜单对象

/// </summary>

/// <param name="m"></param>

/// <returns></returns>

[HttpPatch]

public IActionResult Update(Menu m)

{

    try

    {

        Menu oldMenu = _Context.Menu.Find(m.MenuId);

        if (oldMenu == null)

            return NotFound("你要访问的菜单不存在或已经删除");

        oldMenu.MenuName = m.MenuName;

        oldMenu.SortNumber = m.SortNumber;

        oldMenu.ModelId = m.ModelId;

        _Context.SaveChanges();

        return Ok();

    }

    catch (Exception ex)

    {

        return Content(ex.Message);

    }

}

/// <summary>

/// 删除模块

/// </summary>

/// <param name="ids"></param>

/// <returns></returns>

[HttpDelete]

public IActionResult Delete(string ids)

{

    try

    {

        if (string.IsNullOrEmpty(ids))

            throw new Exception("获取id参数失败");

        List<int> idList = ids.Split(',').Select(q => Convert.ToInt32(q)).ToList();

        List<Model> list = _Context.Model.Where(q => idList.Contains(q.ModelId)).ToList();

        _Context.Model.RemoveRange(list);

        int count = _Context.SaveChanges();

        //使用OkObjectResult 前台jquery自动解析为object对象,不需要进行反序列化处理

        //返回的不是字符串

        return Ok(new

        {

            msg = $"删除成功,总共删除{count}条数据"

        });

    }

    catch (Exception ex)

    {

        //使用ContentResult返回字符串处理

        return Content(ex.Message);

    }

}

3.jQuery ajax代码


//Get获取列表数据

function testOne() {

    $.get(urlHelper.getApi('menu'), {}, function (data) {

        console.info(data);

        var menu1 = new Vue({

            el: '#menu1', data: {

                result: data

            }

        });

    })

}

testOne();

//添加菜单

var example2 = new Vue({

    el: '#example-2',

    data: {

        name:'添加菜单'

    },

    //在 methods 对象中定义方法

    methods: {

        addMenu: function (event) {

            //使用 Post提交添加菜单

            var _this = this;

            this.name = '正在提交...';

            $.post(urlHelper.getApi('menu'), {

                ModelName: '测试菜单5',

                SortNumber:5

            }, function (data) {

                console.info(data);

                _this.name = '提交成功';

            });

        },

        updateModel: function (event) {

            //使用put提交修改模块

            var _this = this;

            $.ajax({

                url: urlHelper.getApi('menu'),

                type: 'put',

                data: {

                    ModelId: '4',

                    ModelName: '模块abc',

                    SortNumber: 4

                },

                success: function (data) {

                    console.info(data);

                    if (data == true)

                        alert('修改成功');

                    else alert('修改失败');

                }

            });

        }

    }

});

//修改菜单、删除模块

var btngroup1 = new Vue({

    el: '#btngroup1',

    data: {

        name: '修改菜单',

        name1: '删除模块'

    },

    methods: {

        updateMenu: function (e) {

            var _this = this;

            //使用patch 方式修改菜单

            $.ajax({

                url: urlHelper.getApi('menu'),

                type:'patch',

                data: {

                    MenuID: 1,

                    MenuName: '测试菜单One',

                    SortNumber: 100,

                    ModelID:2

                },

                success: function (data) {

                    console.info(data);

                }

            });

        },

        deleteMenu: function (e) {

            //使用delete 提交方式删除模块

            $.ajax({

                url: urlHelper.getApi('menu'),

                type: 'delete',

                data: {

                    ids:[1003]

                },

                success: function (data) {

                    console.info(data);

                },

                error: function (data) {

                    console.info(data);

                }

            });

        }

    }

});

根据HttpMethod的Template路由示例


1.API定义


/*

* API 定义

*  GET     api/menu/{id} 获取指定ID的菜单对象

*  GET     api/menu/getmodel 获取模块列表内容

*/

[HttpGet("{id}")]

public IActionResult Get(int ID)

{

    Menu m = _Context.Menu.Find(ID);

    if (m == null)

        return NotFound();

    return Json(m);

}

//特别说明:路由中的Template的值不可以包含斜杠/

[HttpGet("getmodel")]  

public IActionResult GetModel()

{

    List<Model> list = _Context.Model.ToList();

    return Json(list);

}

Jquery 的ajax代码


//其他Get方式测试

var btngroup2 = new Vue({

    el: '#btngroup2',

    data: {

        name: '获取菜单对象',

        name1: '获取模块列表',

        item: {} //Vue的对象绑定,没有的情况下需要一个空对象,不然报错

    },

    methods: {

        getMenu: function (e) {

            var _this = this;

            //链接地址格式 :http://localhost:50000/api/menu/1/

            $.get(urlHelper.getApi('menu','1'), { }, function (data) {

                console.info(data);

                _this.item = data;

            });

        },

        getModel: function (e) {

            var _this = this;

            $.get(urlHelper.getApi('menu', 'getmodel'), {}, function (data) {

                console.info(data);

            })

        }

    }

});

 7.Startup


        /// <summary>

        /// 此方法由运行时调用。使用此方法向容器添加服务。

        /// </summary>

        /// <param name="services">For more information on how to configure your application, visit https://go.microsoft.com/fwlink/?LinkID=398940</param>

        /// <returns>DI容器</returns>

        public IServiceProvider ConfigureServices(IServiceCollection services)

        {

            //添加MVC

            services.AddMvc(

                    // 配置异常过滤器

                    config => { config.Filters.Add(typeof(CustomExceptionFilter)); }

                 )

                // 设置json序列化方式

                .AddJsonOptions(mvcJsonOptions =>

                {

                    //忽略循环引用

                    mvcJsonOptions.SerializerSettings.ReferenceLoopHandling = ReferenceLoopHandling.Ignore;

                    //不使用驼峰样式的key

                    mvcJsonOptions.SerializerSettings.ContractResolver = new DefaultContractResolver();

                    //设置时间格式

                    mvcJsonOptions.SerializerSettings.DateFormatString = Const.yyyyMMddHHmmss;

                })

                .SetCompatibilityVersion(CompatibilityVersion.Version_2_1);


            services.AddCors(options =>

            {

                options.AddPolicy("AllowSpecificOrigins",

                    builder =>

                    {

                        builder.AllowAnyOrigin()

                            .AllowAnyMethod()

                            .AllowAnyHeader()

                            .AllowCredentials();

                    });

            });


            // 注册服务并且实例化AutoFac替换默认容器

            var containerBuilder = new ContainerBuilder(); //实例化 AutoFac  容器  

            // 注册用户服务

            containerBuilder.RegisterType<UserService>().As<IUserService>();

            containerBuilder.Populate(services);

            ApplicationContainer = containerBuilder.Build();

            return new AutofacServiceProvider(ApplicationContainer); //第三方IOC接管 core内置DI容器

        }

        /// <summary>

        /// 此方法由运行时调用。使用此方法配置HTTP请求管道。

        /// </summary>

        /// <param name="app">app</param>

        /// <param name="env">env</param>

        public void Configure(IApplicationBuilder app, IHostingEnvironment env)

        {

            //if (env.IsDevelopment())

            //{

            //    app.UseDeveloperExceptionPage();

            //}


            //app.UseExceptionHandler(

            //    options => {

            //        options.Run(

            //            async context =>

            //            {

            //                context.Response.StatusCode = (int)HttpStatusCode.OK;

            //                context.Response.ContentType = "text/html";

            //                var ex = context.Features.Get<IExceptionHandlerFeature>();

            //                if (ex != null)

            //                {

            //                    var err = $"<h1>Error: {ex.Error.Message}</h1>{ex.Error.StackTrace }";

            //                    await context.Response.WriteAsync(err).ConfigureAwait(false);

            //                }

            //            });

            //    }

            //);


            //启动跨域

            app.UseCors("AllowSpecificOrigins");


            //注入MVC路由

            app.UseMvc();

        }



Dapper入门

在NuGet中搜索Dapper安装即可

使用实体点击打开

点击查看原文

访问量不大的项目我都是用EF写数据库操作,因为EF除了速度上慢以外,但开发效率极快,省略了很多sql写法,并能很方便的调用外键、集合等信息,用EF写项目最爽的事。不过有些项目网站要考虑运行速度,这时不得不用其它的ORM框架,我常用dapper,因为它效果快,而且写sql非常灵活,接下来面写一些方法看看dapper的使用


1、连接语句

var conn = new SqlConnection(ConfigurationManager.ConnectionStrings["SqlDiagnosticsDb"].ConnectionString);

使用dapper不需要考虑conn是否连接,在执行dapper时自行判断 open状态,如果没有打开它会自己打开。


2、insert

string query = "INSERT INTO Book(Name)VALUES(@name)";

conn.Execute(query, book);

book类中有name属性,就可以这样方便去写,当然也可以写成


string query = "INSERT INTO Book(Name)VALUES(@name)";

 conn.Execute(query,  new{@name=book.name});


3、update

string query = "UPDATE Book SET  Name=@name WHERE id =@id";

 conn.Execute(query, book);


4、 delete

string query = "DELETE FROM Book WHERE id = @id";

conn.Execute(query, book);

conn.Execute(query, new { id = id });


5、query

string query = "SELECT * FROM Book";

//无参数查询,返回列表,带参数查询和之前的参数赋值法相同。

 conn.Query<Book>(query).ToList();


 //返回单条信息

 string query = "SELECT * FROM Book WHERE id = @id";

 book = conn.Query<Book>(query, new { id = id }).SingleOrDefault();  


6、 传统sql in (1,2,3) 用dapper就这样写

conn.Query<Users>("SELECT * FROM Users s WHERE s.id IN @ids ",new { ids = new int[]{1,2,3}})

conn.Query<Users>("SELECT * FROM Users s WHERE s.id IN @ids ",new { ids = IDs.ToArray()})

在dapper因为安全性,不能直接用sql接接    要采用参数化,


7、批量插入

conn.Execute(@"insert MyTable(colA, colB) values (@a, @b)", new[] { new { a=1, b=1 }, new { a=2, b=2 }, new { a=3, b=3 } })

也可以直接写入一个集合


conn.Execute("insert user(name) values(@name)",users)

这里users是一个user表的对象集合,可一次把集合中的所有数据插入到数据表中。


8、多表查询

//查询图书时,同时查找对应的书评,并存在List中。实现1--n的查询操作

string query = "SELECT * FROM Book b LEFT JOIN BookReview br ON br.BookId = b.Id WHERE b.id = @id";

Book lookup = null;

//Query<TFirst, TSecond, TReturn>

 var b = conn.Query<Book, BookReview, Book>(query,

  (book, bookReview) =>

  {

     //扫描第一条记录,判断非空和非重复

    if (lookup == null || lookup.Id != book.Id)

      lookup = book;

    //书对应的书评非空,加入当前书的书评List中,最后把重复的书去掉。

    if (bookReview != null)

      lookup.Reviews.Add(bookReview);

     return lookup;

  }, new { id = id }).Distinct().SingleOrDefault();

return b;


多表联合查询是比较麻烦一些,到现在不是完全明白,多看几个例子

var sql =  @"select * from Posts p join Users u on u.Id = p.OwnerId Order by p.Id";

var data = conn.Query<Post, User, Post>(sql, (post, user) => { post.Owner = user; return post;},splitOn:"id");

 

Post类和User类,它们存在外键, conn.Query返回的类型是最后一个参数Post, 其中Post中有一属性Owner是User对象,在(post, user)=>lamda中指定了Owner值,上边的代码中的splitOn是ID,运行时,会从查询结果所有字段列表的最后一个字段开始进行匹配,一直到找到Id这个字段(大小写忽略),找到的第一个ID字段匹配User类的ID属性,那么从ID到最后一个字段都属于User,ID以前的字段都被影射到Post,通过(post, user) => { return post;},把两个类的实例解析出来。


9、三表查询,一个是关联主键表(单个对象),一个是关联外键表(集合)。

    public partial class UserInfo  

        {  

            public UserInfo()  

            {  

                this.Persion = new HashSet<Persion>();  

                this.MyTYC = new HashSet<MyTYC>();  

            }  

          

            public int id { get; set; }  

            public string name { get; set; }  

            public Nullable<System.DateTime> createTime { get; set; }  

            public Movies Movies { get; set; }  

            public virtual ICollection<MyTYC> MyTYC { get; set; }  

        }  


    public class Movies  

        {  

            public int ID { get; set; }  

            public string Title { get; set; }  

            public string ReleaseDate { get; set; }  

            public string Genre { get; set; }  

            public string Price { get; set; }  

            public UserInfo UserInfo { get; set; }  

      

        }  


    public partial class MyTYC  

        {  

            public int id { get; set; }  

            public string name { get; set; }  

      

        }  


string sql = @"select * from UserInfo u   

inner join [Movies].dbo.Movies m on u.id=m.ID   

inner join MyTYC t on u.id=t.id";  

            var data = conn.Query<UserInfo, Movies, MyTYC, UserInfo>(sql, (u, m, t) => { u.Movies = m; u.MyTYC.Add(t); return u; }); 

注意这里的对象和集合的获取方法:u.Movies = m; u.MyTYC.Add(t);


10、多结果查询

var sql = @"select * from Customers where CustomerId = @id;

 select * from Orders where CustomerId = @id;

 select * from Returns where CustomerId = @id";


 using (var multi = connection.QueryMultiple(sql, new {id=selectedId}))

 {

        var customer = multi.Read<Customer>().Single();    

        var orders = multi.Read<Order>().ToList();

        var returns = multi.Read<Return>().ToList();

 }


再来一个

class Program  

    {  

  

        //创建连接对象  

        protected static SqlConnection GetConnection()  

        {  

            var connection = new SqlConnection("Data Source=.;Initial Catalog=TestDB;Integrated Security=True");  

            connection.Open();  

            return connection;  

        }  

  

        static void Main(string[] args)  

        {  

            //测试输出多个结果集  

            var sql = @"INSERT INTO [dbo].[Student] ([Name]) VALUES ('A1'); select @@IDENTITY as A;  

                        INSERT INTO [dbo].[Student] ([Name]) VALUES ('B1'); select @@IDENTITY as A;  

                        INSERT INTO [dbo].[Student] ([Name]) VALUES ('C1'); select @@IDENTITY as A";  

  

            //初始化数据库连接  

            using (SqlConnection connection = GetConnection())  

            {                  

                List<int> ilist = new List<int>();  

                //执行查询,获取结果集集合  

                var multi = connection.QueryMultiple(sql);  

  

                //遍历结果集  

                while(!multi.IsConsumed)  

                {  

                    //读取当前结果集  

                    var result = multi.Read().ToList()[0].A;  

                    if (result != null)  

                    {  

                        ilist.Add(Convert.ToInt32(result));  

                    }  

                }  

                //for(int i = 0;i<3;i++)  

                //{  

                //    var result = multi.Read().ToList()[0].A;  

                //    if (result != null)  

                //    {  

                //        ilist.Add(Convert.ToInt32(result));  

                //    }  

                //}  

                foreach (var item in ilist)  

                {  

                    Console.WriteLine(item.ToString());  

                }  

            }  

            Console.ReadLine();  

        }  

    } 


11、如果某一代码中多次操作数据库,可以把conn设置为打开,最后时再close,


conn.open()

conn.Query(.....

.....

for....

.....

conn.close()


12. 存储过程

var user = cnn.Query<User>("spGetUser", new {Id = 1},ommandType: CommandType.StoredProcedure).SingleOrDefault();


13. 特殊sql的处理,Dapper中in,like的使用


list = conn.Query<Dog>("SELECT * FROM Dogs WHERE id IN @ids ", new { ids = new long[] { list[0].Id, list[1].Id, list[2].Id } }).ToList();

list = conn.Query<Dog>("SELECT * FROM Dogs WHERE name LIKE @name ", new { name = $"%{name}%" }).ToList(); 






Entity Framework Core (EFCore)学习




ServerPUSH的使用

Server Push 有以下几个主要好处:


1.减少页面加载时间

传统的 HTTP 请求-响应模型中,浏览器需要先接收并解析 HTML,然后再发送额外的请求来获取页面所需的其他资源,如 CSS、JavaScript 和图片等。这会导致多次网络往返,增加了页面加载时间。

使用 Server Push,服务器可以在发送 HTML 响应的同时,主动推送其他关键资源。这样浏览器就不需要等待 HTML 解析完成后再去请求这些资源,从而减少了网络延迟,加快了页面加载速度。


2.优化关键渲染路径

关键渲染路径是指浏览器将 HTML、CSS 和 JavaScript 转换为屏幕上的像素所经过的一系列步骤。优化关键渲染路径可以显著改善用户体验。

使用 Server Push,可以优先推送关键的 CSS 和 JavaScript 文件,使浏览器尽早开始渲染页面。这可以减少用户看到空白页面的时间,提升了用户体验。


3.减轻服务器压力

在传统模型中,如果一个页面包含许多资源,浏览器需要发送多个请求来获取这些资源。这会增加服务器的负载,因为服务器需要处理更多的请求。

使用 Server Push,服务器可以在一个请求-响应周期内发送多个资源,减少了总的请求数量。这可以减轻服务器的压力,提高服务器的性能。


4.更好的缓存利用

当浏览器收到服务器推送的资源时,它会将这些资源缓存起来。当用户再次访问同一页面或者访问使用相同资源的其他页面时,浏览器可以直接从缓存中读取这些资源,而不需要再次请求服务器。这可以进一步加快页面加载速度,减轻服务器负担。

总的来说,Server Push 是一项强大的技术,可以显著改善 Web 应用的性能和用户体验。但同时也需要谨慎使用,避免推送不必要的资源,以免浪费带宽和计算资源


下面是一个使用 C# 和 ASP.NET Core 实现 Server Push 的例子。

首先,确保你的项目设置为使用 HTTP/2。在 Startup.cs 文件的 ConfigureServices 方法中添加以下代码:

services.AddHttp2();

然后,在 Startup.cs 文件的 Configure 方法中,你可以使用 UseHttp2 中间件和 HttpContext.PushAsync 方法来实现 Server Push。下面是一个例子:

app.UseRouting();


app.UseEndpoints(endpoints =>

{

    endpoints.MapGet("/", async context =>

    {

        // 推送 CSS 文件

        await context.PushAsync("/css/site.css", new PushOptions

        {

            Method = "GET",

            Headers = new HeaderDictionary { ["Content-Type"] = "text/css" }

        });


        // 推送 JavaScript 文件

        await context.PushAsync("/js/site.js", new PushOptions

        {

            Method = "GET",

            Headers = new HeaderDictionary { ["Content-Type"] = "application/javascript" }

        });


        // 发送 HTML 响应

        await context.Response.WriteAsync(@"

            <html>

                <head>

                    <link rel=""stylesheet"" href=""/css/site.css"">

                </head>

                <body>

                    <h1>Hello, world!</h1>

                    <script src=""/js/site.js""></script>

                </body>

            </html>");

    });

});

在这个例子中:

我们首先调用 context.PushAsync 方法来推送 CSS 文件。PushOptions 对象允许你设置推送请求的 HTTP 方法和头信息。

然后我们同样推送了 JavaScript 文件。

最后,我们发送 HTML 响应。注意,即使 HTML 中引用了 CSS 和 JavaScript 文件,浏览器也不会再次请求这些文件,因为它们已经通过 Server Push 发送了。

请记住,Server Push 只适用于 HTTP/2。如果客户端不支持 HTTP/2,服务器会自动回退到传统的请求-响应模式。


另外,过度使用 Server Push 可能会适得其反,因为它会消耗服务器和客户端的资源。因此,只推送那些对页面渲染至关重要的资源,如 CSS、JavaScript 和字体文件等。


跨平台的DLL

.NET Standard

.NET Standard规定了一系列需要被所有.NET Core、.NET Framework及Xamarin等共同实现的API,包括有哪些类、有哪些方法、参数和返回值是什么等



ObservableCollection<T> 类 表示一个动态数据集合

ObservableCollection<T> 类 表示一个动态数据集合,它是实现了INotifyPropertyChanged 接口的数据集合,在添加项、移除项或刷新整个列表时,此集合将提供通知

public class Goods

{

public int Order { get; set; }

public string Name { get; set; }

public bool IsSoldOut { get; set; }

}

static void Main(string[] args)

{

ObservableCollection<Goods> ocGoods = new ObservableCollection<Goods>();

ocGoods.Add(new Goods() { Order = 1, Name = "钢笔", IsSoldOut = true });

ocGoods.Add(new Goods() { Order = 2, Name = "羽毛球", IsSoldOut = false });

ocGoods.Add(new Goods() { Order = 3, Name = "毛巾", IsSoldOut = false });

ocGoods.Add(new Goods() { Order = 4, Name = "零食", IsSoldOut = true });


//按照IsSoldOut降序排列,即true在前面

ocGoods = new ObservableCollection<Goods>(ocGoods.OrderByDescending(item => item.IsSoldOut));

foreach (Goods item in ocGoods)

{

Console.WriteLine("Order:{0} Name:{1} IsSoldOut:{2}", item.Order, item.Name, item.IsSoldOut.ToString());

}

Console.ReadLine();

}


//按照IsSoldOut降序排列,即true在前面

ocGoods = new ObservableCollection<Goods>(ocGoods.OrderByDescending(item => item.IsSoldOut));



ObservableCollection<T> 是一个非常有用的集合类,它在数据绑定场景中特别有用,因为它实现了 INotifyCollectionChanged 接口,当集合中的项被添加、移除或者改变时,它会通知订阅者这些变化。这对于保持 UI 和数据模型的一致性非常重要。

在 .NET MAUI 中使用 ObservableCollection<T> 通常涉及到与 CollectionView 控件或其他列表控件一起工作。下面是一个简单的例子,展示如何在 MAUI 应用中使用 ObservableCollection<T> 来实现增删改查功能。

首先,定义一个简单的数据模型类:


public class Message

{

    public string Text { get; set; }

}

然后,在 XAML 文件中定义一个 CollectionView 来显示消息列表:


xml

<ContentPage xmlns="http://schemas.microsoft.com/dotnet/2021/maui"

             xmlns:x="http://schemas.microsoft.com/winfx/2009/xaml"

             x:Class="MauiApp.MainPage">

    <ContentPage.Content>

        <StackLayout>

            <CollectionView x:Name="MessagesList" ItemsSource="{Binding Messages}">

                <CollectionView.ItemTemplate>

                    <DataTemplate>

                        <ViewCell>

                            <Label Text="{Binding Text}" />

                        </ViewCell>

                    </DataTemplate>

                </CollectionView.ItemTemplate>

            </CollectionView>

            

            <Entry Placeholder="Enter a message" x:Name="MessageInput" />

            <Button Text="Add" Clicked="OnAddClicked" />

            <Button Text="Remove Selected" Clicked="OnRemoveSelectedClicked" />

        </StackLayout>

    </ContentPage.Content>

</ContentPage>

接着,在对应的 .cs 文件中实现数据绑定和操作方法:


csharp

public partial class MainPage : ContentPage

{

    public ObservableCollection<Message> Messages { get; } = new ObservableCollection<Message>();


    public MainPage()

    {

        InitializeComponent();

        BindingContext = this;


        // 初始化一些数据

        Messages.Add(new Message { Text = "Hello, World!" });

        Messages.Add(new Message { Text = "Welcome to MAUI!" });

    }


    private void OnAddClicked(object sender, EventArgs e)

    {

        if (!string.IsNullOrWhiteSpace(MessageInput.Text))

        {

            Messages.Add(new Message { Text = MessageInput.Text });

            MessageInput.Text = string.Empty; // 清空输入框

        }

    }


    private void OnRemoveSelectedClicked(object sender, EventArgs e)

    {

        if (MessagesList.SelectedItem is Message selectedItem)

        {

            Messages.Remove(selectedItem);

            MessagesList.SelectedItem = null; // 清除选择项

        }

    }

}

在这个例子中,我们创建了一个 ObservableCollection<Message> 并将其绑定到了 CollectionView 的 ItemsSource 属性上。OnAddClicked 方法负责向集合中添加新消息,而 OnRemoveSelectedClicked 方法则负责删除当前选中的消息。


注意,CollectionView 会自动监听 ObservableCollection<T> 的变化,并在集合发生变化时更新其视图。因此,当你添加或删除项时,UI 会自动更新以反映最新的数据状态。












Top