.NET框架设计—常被忽视的框架设计技巧

简介:

阅读目录:

  • 1.开篇介绍

  • 2.元数据缓存池模式(在运行时构造元数据缓存池)

    • 2.1.元数据设计模式(抽象出对数据的描述数据)

    • 2.2.借助Dynamic来改变IOC、AOP动态绑定的问题

    • 2.3.元数据和模型绑定、元数据应该隐藏在Model背后、元数据与DSL的关系


  • 3.链式配置Dynamic模式(爱不释手的思维习惯编程)

  • 4.委托工厂模式(要优于常见的 工厂,概念更加准确,减少污染)

  • 5.规则外挂(视委托为特殊的规则对象原型)

1】开篇介绍

通过上一篇的“.NET框架设计—常被忽视的C#设计技巧”一文来看,对于框架设计的技巧还是有很多人比较有兴趣的,那么框架设计思想对于我们日常开发来说其实并不是很重要,但是对于我们理解框架背后的运行原理至关重要;当我们使用着LINQ灵活的语法的同时我们是否能理解它的背后运行原理、设计原理更深一点就是它的设计模式及复杂的对象模型;

从一开始学习.NET我就比较喜欢框架背后的设计模型,框架提供给我们的使用接口是及其简单的,单纯从使用上来看我们不会随着对框架的使用时间而增加我们对框架内部设计的理解,反而会养成一样哪来即用的习惯,我们只有去了解、深挖它的内部设计原理才是我们长久学习的目标;因为框架的内部设计模式是可以提炼出来并被总结的;

这篇文章总结了几个我最近接触的框架设计思想,可以称他们为模式;由于时间关系,这里只是介绍加一个简单的介绍和示例让我们能基本的了解它并且能在日后设计框架的时候想起来有这么一个模式、设计方式可以借鉴;当然,这每一节都是一个很大主题,用的时候在去细心的分析学习吧;

2】元数据缓存池模式(在运行时构造元数据缓存池)

很多框架都有将特性放在属性上面用来标识某种东西,但是这种方式使用不当的话会对性能造成影响;再从框架设计原则来讲也是对DomainModel极大的污染,从EntityFramework5.0之前的版本我们就能体会到了,它原本是将部分Attribute加在了Entity上的,但是这毕竟是业务代码的核心,原则上来说这不能有任何的污染,要绝对的POJO;后来5.0之后就完全独立了DomainModel.Entity,所有的管理都在运行时构造绑定关系,因为它有EDMX元数据描述文件;

那么这些Attribute其实本质是.NET在运行时的一种元数据,主要的目的是我们想在运行时将它读取出来,用来对某些方面的判断;那么现在的问题是如果我们每次都去读取这个Attribute是必须要走反射机制,当然你可以找一些框架来解决这个问题;(我们这里讨论的是你作为开发框架的设计者!)

反射影响性能这不用多讲了,那么常规的做法是会在第一次反射之后将这些对象缓存起来,下次再用的时候直接在缓存中读取;这没有问题,这是解决了反射的性能问题,那么你的Attribute是否还要加在DomainModel中呢,如果加的话随着代码量的增加,这些都会成为后面维护的成本开销;那么我们如何将干净的POJO对象提供给程序员用,但是在后台我们也能对POJO进行强大的控制?这是否是一种设计问题?

2.1】元数据设计模式(抽象出对数据的描述数据)

我一直比较关注对象与数据之间的关系,面向对象的这种纵横向关系如何平滑的与E-R实体关系模型对接,这一直是复杂软件开发的核心问题;这里就用它来作为本章的示例的基本概要;

我们有一个基本的DomainModel聚合,如何在不影响本身简洁性的情况下与E-R关系对接,比如我们在对聚合进行一个Add操作如何被映射成对数据库的Insert操作;我们来看一下元数据设计模式思想;

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
/*==============================================================================
  * Author:深度训练
  * Create time: 2013-08-04
  * Blog Address:http://www.cnblogs.com/wangiqngpei557/
  * Author Description:特定领域软件工程实践;
  *==============================================================================*/
namespace  ConsoleApplication1.DomainModel
{
     /// <summary>
     /// Employee.<see cref="DomainModel.Employee"/>
     /// </summary>
     public  class  Employee
     {
         /// <summary>
         /// Primary id.
         /// </summary>
         public  string  EId {  get set ; }
         /// <summary>
         /// Name.
         /// </summary>
         public  string  Name {  get set ; }
         /// <summary>
         /// Sex.<see cref="DomainModel.SexType"/>
         /// </summary>
         public  SexType Sex {  get set ; }
         /// <summary>
         /// Address.
         /// </summary>
         public  Address Address {  get set ; }
     }
}


这里有一个以Employee实体为聚合根的聚合,里面包含一些基本的属性,特别需要强调的是Sex属性和Address,这两个属性分别是Complex类型的属性; Complex类型的属性是符合面向对象的需要的,但是在关系型数据库中是很难实现的,这里就需要我们用元数据将它描述出来并能在一些行为上进行控制;

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
/*==============================================================================
  * Author:深度训练
  * Create time: 2013-08-04
  * Blog Address:http://www.cnblogs.com/wangiqngpei557/
  * Author Description:特定领域软件工程实践;
  *==============================================================================*/
namespace  ConsoleApplication1.DomainModel
{
     /// <summary>
     /// Address .
     /// </summary>
     public  struct  Address
     {
         /// <summary>
         /// Address name.
         /// </summary>
         public  string  AddressName {  get set ; }
     }
}


这是Address类型的定义;

1
2
3
4
5
6
7
8
9
10
11
namespace  ConsoleApplication1.DomainModel
{
     /// <summary>
     /// Sex type.
     /// </summary>
     public  enum  SexType
     {
         Male,
         Female
     }
}


这是SexType类型的定义;都比较简单;


只有这样我们才能对DomainModel进行大面积的复杂设计,如果我们不能将数据对象化我们无法使用设计模式,也就谈不上扩展性;

图1:

1009349_1375614540h7VU.jpg

这是我们的对象模型,那么我们如何将它与数据库相关的信息提取出来形成独立的元数据信息,对元数据的抽取需要动、静结合才行;

什么动、静结合,我们是否都会碰见过这样的问题,很多时候我们的代码在编译时是确定的,但是有部分的代码需要在运行时动态的构造,甚至有些时候代码需要根据当前的IDE来生成才行,但是最终在使用的时候这些在不同阶段生成的代码都需要结合起来变成一个完整的元数据对象;

框架在很多时候需要跟IDE结合才能使使用变的顺手,比如我们在开发自己的ORM框架如果不能直接嵌入到VisualStudio中的话,用起来会很不爽;当我们用自己的插件去连接数据库并且生成代码的时候,有部分的元数据模型已经在代码中实现,但是有部分需要我们动态的去设置才行;

我们来看一下关于元数据的基础代码;

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
/*==============================================================================
  * Author:深度训练
  * Create time: 2013-08-04
  * Blog Address:http://www.cnblogs.com/wangiqngpei557/
  * Author Description:特定领域软件工程实践;
  *==============================================================================*/
namespace  ORM.Meta
{
     using  System;
     using  System.Collections.Generic;
     using  System.Linq;
     using  System.Text;
     using  System.Threading.Tasks;
     /// <summary>
     /// Data source context.
     /// </summary>
     public  abstract  class  DataBaseContext : List<MetaTable>, IDisposable
     {
         /// <summary>
         /// Data base name.
         /// </summary>
         protected  string  DataBaseName {  get set ; }
         /// <summary>
         /// Connection string.
         /// </summary>
         protected  string  ConnectionString {  get set ; }
         /// <summary>
         /// Provider child class add table.
         /// </summary>
         /// <param name="table"></param>
         protected  virtual  void  AddTable(MetaTable table)
         {
             this .Add(table);
         }
         /// <summary>
         /// Init context.
         /// </summary>
         protected  virtual  void  InitContext() { }
         public  void  Dispose() { }
     }
}


这表示数据源上下文,属于运行时元数据的基础设施;

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
/*==============================================================================
  * Author:深度训练
  * Create time: 2013-08-04
  * Blog Address:http://www.cnblogs.com/wangiqngpei557/
  * Author Description:特定领域软件工程实践;
  *==============================================================================*/
namespace  ORM.Meta
{
     using  System.Collections.Generic;
     using  System.Linq;
     /// <summary>
     /// Database Table meta.
     /// </summary>
     public  class  MetaTable : List<MetaColumn>
     {
         /// <summary>
         /// Table name.
         /// </summary>
         public  string  Name {  get set ; }
         /// <summary>
         /// Entity name.
         /// </summary>
         public  string  EntityName {  get set ; }
         /// <summary>
         /// Get column by column name.
         /// </summary>
         /// <param name="name">Column name.</param>
         /// <returns><see cref="ORM.MetaColumn"/></returns>
         public  MetaColumn GetColumnByName( string  name)
         {
             var  column =  from  item  in  this .ToList()  where  item.CoumnName == name  select  item;
             return  column.FirstOrDefault();
         }
     }
}

简单的表示一个Table,里面包含一系列的Columns;要记住在设计元数据基础代码的时候将接口留出来,方便在IDE中植入初始化元数据代码;

图2:

1009349_1375614541WJm8.jpg

到目前为止我们都是在为元数据做基础工作,我们看一下有系统生成的声明的元数据代码;

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
/*==============================================================================
  * Author:深度训练
  * Create time: 2013-08-04
  * Blog Address:http://www.cnblogs.com/wangiqngpei557/
  * Author Description:特定领域软件工程实践;
  *==============================================================================*/
namespace  ConsoleApplication1.Repository
{
     using  System;
     using  System.Collections.Generic;
     using  System.Linq;
     using  System.Text;
     using  System.Threading.Tasks;
     /// <summary>
     /// IDE Builder.
     /// </summary>
     public  class  DesignBuilder_DataBaseContext : ORM.Meta.DataBaseContext
     {
         //this begin IDE builder.
         protected  override  void  InitContext()
         {
             ORM.Meta.MetaTable metaTable =  new  ORM.Meta.MetaTable() { Name =  "TB_Employee" , EntityName =  "Employee"  };
             metaTable.Add( new  ORM.Meta.MetaColumn()
             {
                 CoumnName =  "EId" ,
                 DataType = ORM.Meta.DataType.NVarchar
             });
             metaTable.Add( new  ORM.Meta.MetaColumn()
             {
                 CoumnName =  "Name" ,
                 DataType = ORM.Meta.DataType.NVarchar
             });
             metaTable.Add( new  ORM.Meta.MetaColumn()
             {
                 CoumnName =  "Sex" ,
                 DataType = ORM.Meta.DataType.Int
             });
             metaTable.Add( new  ORM.Meta.MetaColumn()
             {
                 CoumnName =  "Address" ,
                 DataType = ORM.Meta.DataType.NVarchar
             });
             this .AddTable(metaTable);
         }
         //end
     }
}


我假设这是我们框架在IDE中生成的部分元数据代码,当然你可以用任何方式来存放这些元数据,但是最后还是要去对象化;

图3:

1009349_1375614543bRbQ.jpg

这个目录你可以直接隐藏,在后台属于你的框架需要的一部分,没有必要让它污染项目结构,当然放出来也有理由;如果想让你的LINQ或者表达式能直接穿过你的元数据上下文你需要直接扩展;

1
2
3
4
5
6
7
static  void  Main( string [] args)
         {
             using  (Repository.DesignBuilder_DataBaseContext context =  new  Repository.DesignBuilder_DataBaseContext())
             {
                 var  employee =  from  emp  in  context.Employee  where  emp.EId ==  "Wqp123"  select  emp;
             }
         }


这里所有的代码看上去很简单,没有多高深的技术,这也不是本篇文章的目的,任何代码都需要设计的驱动才能产生价值,我们构建的基础代码都是元数据驱动;当你在运行时把这些元数据放入Cache,既不需要加Attribute也不需要反射反而活动了更大程度上的控制,但是要想构建一个能用的元数据结构需要结合具体的需求才行;

2.2】借助Dynamic来改变IOC、AOP动态绑定的问题

要想在运行时完全动态的绑定在编译时定义的对象行为是需要强大的IOC框架支撑的,这样的框架我们是做不来的或者需要很多精力,得不偿失;对于元数据设计需要将AOP通过IOC的方式注入,在使用的时候需要改变一下思路,AOP的所有的切面在编译时无法确定,后期通过IOC的方式将所有的行为注入;这里我们需要使用动态类型特性;

使用Dynamic之后我们很多以往不能解决问题都可以解决,更向元编程跨进了一步;对于IOC、AOP的使用也将变的很简单,也有可能颠覆以往IOC、AOP的使用方式;而且动态编程将在很大程度上越过设计模式了,也就是设计模式的使用方式在动态编程中将不复存在了;

1
2
3
4
5
6
7
using  (Repository.DesignBuilder_DataBaseContext context =  new  Repository.DesignBuilder_DataBaseContext())
             {
                 var  employees =  from  emp  in  context.Employee  where  emp.EId ==  "Wqp123"  select  emp;
                 Employee employee =  new  Employee() { EId =  "Wqp123"  };
                 var  entityOpeartion = DynamicBehavior.EntityDymanicBehavior.GetEntityBehavior<Employee>(employee);
                 entityOpeartion.Add();
             }



1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
/*==============================================================================
  * Author:深度训练
  * Create time: 2013-08-04
  * Blog Address:http://www.cnblogs.com/wangiqngpei557/
  * Author Description:特定领域软件工程实践;
  *==============================================================================*/
namespace  ConsoleApplication1.DynamicBehavior
{
     using  System;
     using  System.Dynamic;
     public  class  EntityDymanicBehavior
     {
         public  static  dynamic GetEntityBehavior<TEntity>(TEntity entity)
         {
             //auto mark entity behavior
             dynamic dy =  new  ExpandoObject();
             //load meta data mark dynamic behavior
             dy.Entity = entity;
             dy.Add =  new  Action(() =>
             {
                 Console.WriteLine( "Action Add "  + entity.GetType());
             });
             return  dy;
         }
     }
}


图4:

1009349_1375614544ELp0.jpg

画红线的部分是可以抽取来放入扩展方法Add中的,在构造的内部是完全可以进入到元数据缓存池中拿到这些数据然后直接动态生成扩展方法背后的真实方法;

2.3】元数据和模型绑定、元数据应该隐藏在Model背后、元数据与DSL的关系

元数据的绑定应该在运行时动态去完成,这点在以往我们需要大费力气,通过CodeDom、Emit才能完成,但是现在可以通过Dynamic、DLR来完成;思维需要转变一下,动态编程我们以往用的最多的地方在JS上,现在可以在C#中使用,当然你也可以使用专门的动态语言来写更强大的元数据框架,IronRuby、IronPython都是很不错的,简单的了解过Ruby的元数据编程,很强大,如果我们.NET程序员眼馋就用Iron…系列;

在开发复杂的动态行为时尽量使用元数据设计思想,不要把数据和表示数据的数据揉在一起,要把他们分开,在运行时Dynamic绑定;元数据应该在Model的背后应该在DomainModel的背后;

元数据和DSL有着天然的渊源,如果我们能把所有的语句组件化就可以将其封入.NET组件中,在IDE中进行所见即所得的DSL设计,然后生成可以直接运行的Dynamic代码,这可能也是元编程的思想之一吧;

图5:

5

这可能是未来10年要改变的编程路线吧,我只是猜测;最后软件将进一步被自定义;

3】链式配置Dynamic模式(爱不释手的思维习惯编程)

再一次提起链式编程是觉得它的灵活性无话可说,语言特性本身用在哪里完全需求驱动;把链式用来做配置相关的工作非常的合适;我们上面做了元数据配置相关的工作,这里我们试着用链式的方法来改善它;

Dynamic类型本身的所有行为属性都是可以动态构建的,那么我们把它放入链式的方法中去,根据不同的参数来实现动态的添加行为;

扩展Dynamic类型需要使用ExpandoObject开始;

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
/*==============================================================================
  * Author:深度训练
  * Create time: 2013-08-04
  * Blog Address:http://www.cnblogs.com/wangiqngpei557/
  * Author Description:特定领域软件工程实践;
  *==============================================================================*/
namespace  ConsoleApplication1.DynamicBehavior
{
     using  System;
     using  System.Dynamic;
     public  static  class  EntityDynamicBehaviorExtent
     {
         /// <summary>
         /// Add dynamic method.
         /// </summary>
         /// <param name="entity"></param>
         /// <returns></returns>
         public  static  ExpandoObject AddExten( this  ExpandoObject entity)
         {
             dynamic dy = entity  as  dynamic;
             dy.Add =  new  Func<ExpandoObject>(() => { Console.WriteLine( "add "  + entity);  return  entity; });
             return  entity;
         }
         /// <summary>
         /// where  dynamic method.
         /// </summary>
         /// <typeparam name="T"></typeparam>
         /// <param name="entity"></param>
         /// <param name="where"></param>
         /// <returns></returns>
         public  static  ExpandoObject WhereExten<T>( this  ExpandoObject entity, Func<T,  bool where )
         {
             dynamic dy = entity  as  dynamic;
             dy.Where =  where ;
             return  entity;
         }
     }
}


扩展方法需要扩展 ExpandoObject对象,DLR在运行时使用的是ExpandoObject对象实例,所以我们不能够直接扩展Dynamic关键字;

1
2
3
4
5
6
7
8
Employee employee1 =  new  Employee() { EId =  "Wqp123"  };
                 var  dynamicEntity = DynamicBehavior.EntityDymanicBehavior.GetEntityBehavior<Employee>(employee1);
                 (dynamicEntity  as  System.Dynamic.ExpandoObject).AddExten().WhereExten<Employee>(emp =>
                 {
                     Console.WriteLine( "Where Method." );
                     return  emp.EId ==  "Wqp123" ;
                 });
                 dynamicEntity.Add().Where(employee1);


图6:

6

红线部分必须要转换才能顺利添加行为;

4】委托工厂模式(要优于常见的 工厂,概念更加准确,减少污染)

对于工厂模式我们都会熟悉的一塌糊涂,各种各样的工厂模式我们见的多了,但是这种类型的工厂使用方式你还真的没见过;其实这种委托是想部分的逻辑交给外部来处理;

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
/*==============================================================================
  * Author:深度训练
  * Create time: 2013-08-04
  * Blog Address:http://www.cnblogs.com/wangiqngpei557/
  * Author Description:特定领域软件工程实践;
  *==============================================================================*/
namespace  ConsoleApplication1.DomainModel
{
     /// <summary>
     /// Address factory.
     /// </summary>
     /// <returns></returns>
     public  delegate  Address Factory();
     /// <summary>
     /// Employee.<see cref="DomainModel.Employee"/>
     /// </summary>
     public  class  Employee
     {
         public  Employee() { }
         /// <summary>
         /// Mark employee instance.
         /// </summary>
         /// <param name="eID"></param>
         /// <param name="name"></param>
         /// <param name="sex"></param>
         /// <param name="addressFactory">address factory.</param>
         public  Employee( string  eID,  string  name, SexType sex, Factory addressFactory)
         {
             this .EId = eID;
             this .Name = name;
             this .Sex = sex;
             this .Address = addressFactory();
         }
         /// <summary>
         /// Primary id.
         /// </summary>
         public  string  EId {  get set ; }
         /// <summary>
         /// Name.
         /// </summary>
         public  string  Name {  get set ; }
         /// <summary>
         /// Sex.<see cref="DomainModel.SexType"/>
         /// </summary>
         public  SexType Sex {  get set ; }
         /// <summary>
         /// Address.
         /// </summary>
         public  Address Address {  get set ; }
     }
}


我们定义了一个用来创建Employee.Address对象的Factory,然后通过构造函数传入;

1
2
3
4
Employee employee2 =  new  Employee( "Wqp123" "Wqp" , SexType.Male,  new  Factory(() =>
         {
               return  new  Address() { AddressName =  "Shanghai"  };
          }));


这里纯粹为了演示方便,这种功能是不应该在DommianModel中使用的,都是在一些框架、工具中用来做灵活接口用的;

5】规则外挂(视委托为特殊的规则对象原型)

规则外挂其实跟上面的委托工厂有点像,但是绝对不一样的设计思想;如何将规则外挂出去,放入Cache中让运行时可以配置这个规则参数;委托是规则的天然宿主,我们只要将委托序列化进Cache就可以对它进行参数的配置;

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
/*==============================================================================
  * Author:深度训练
  * Create time: 2013-08-04
  * Blog Address:http://www.cnblogs.com/wangiqngpei557/
  * Author Description:特定领域软件工程实践;
  *==============================================================================*/
namespace  ConsoleApplication1.DomainModel.Specification
{
     using  System;
     using  System.Linq.Expressions;
     /// <summary>
     /// Employee add specification.
     /// </summary>
     [Serializable]
     public  class  EmployeeSpecificationAdd : System.Runtime.Serialization.IDeserializationCallback
     {
         /// <summary>
         /// specification.
         /// </summary>
         [NonSerialized]
         private  Func<Employee,  bool > _specification;
         /// <summary>
         /// Gets specification.
         /// </summary>
         public  Func<Employee,  bool > Specificaion {  get  return  _specification; } }
         /// <summary>
         /// employee.
         /// </summary>
         private  Employee Employee {  get set ; }
         /// <summary>
         /// Mark employee specificatoin.
         /// </summary>
         /// <param name="employee"></param>
         public  EmployeeSpecificationAdd(Employee employee)
         {
             this .Employee = employee;
             InitSpecification();
         }
         /// <summary>
         /// Is Check.
         /// </summary>
         /// <returns></returns>
         public  bool  IsCheck()
         {
             return  _specification(Employee);
         }
         public  void  OnDeserialization( object  sender)
         {
             InitSpecification();
         }
         private  void  InitSpecification()
         {
             this ._specification = (emp) =>
             {
                 return  ! string .IsNullOrWhiteSpace(emp.EId) && ! string .IsNullOrWhiteSpace(emp.Name);
             };
         }
     }
}


图7:


7

注意这里的反序列化接口实现,因为Lambda无法进行序列化,也没有必要进行序列化;

1
2
3
4
5
6
7
8
9
10
11
12
EmployeeSpecificationAdd specification =  new  EmployeeSpecificationAdd(employee2);
                 Stream stream = File.Open( "specification.xml" , FileMode.Create);
                 BinaryFormatter formattter =  new  BinaryFormatter();
                 formattter.Serialize(stream, specification);
                 stream.Seek(0, SeekOrigin.Begin);
                 specification = formattter.Deserialize(stream)  as  EmployeeSpecificationAdd;
                 stream.Close();
                 stream.Dispose();
                 if  (specification.IsCheck())
                 {
                     Console.WriteLine( "Ok..." );
                 }


既然能将规则序列化了,就可以把它放在任何可以使用的地方了,配置化已经没有问题了;

示例Demo地址:http://files.cnblogs.com/wangiqngpei557/ConsoleApplication2.zip




 本文转自 王清培 51CTO博客,原文链接:http://blog.51cto.com/wangqingpei557/1263990,如需转载请自行联系原作者

相关文章
|
5天前
|
数据可视化 网络协议 C#
C#/.NET/.NET Core优秀项目和框架2024年3月简报
公众号每月定期推广和分享的C#/.NET/.NET Core优秀项目和框架(每周至少会推荐两个优秀的项目和框架当然节假日除外),公众号推文中有项目和框架的介绍、功能特点、使用方式以及部分功能截图等(打不开或者打开GitHub很慢的同学可以优先查看公众号推文,文末一定会附带项目和框架源码地址)。注意:排名不分先后,都是十分优秀的开源项目和框架,每周定期更新分享(欢迎关注公众号:追逐时光者,第一时间获取每周精选分享资讯🔔)。
|
5天前
|
开发框架 JavaScript 前端开发
5个.NET开源且强大的快速开发框架(帮助你提高生产效率)
5个.NET开源且强大的快速开发框架(帮助你提高生产效率)
112 4
|
5月前
|
开发框架 缓存 Cloud Native
微软发布 .NET 云原生开发框架—— .NET Aspire
微软于 2023-11-14日 发布了 .NET 8 的正式版。伴随着这个重要 .NET 版本的发布,微软也发布了一个全新的 .NET云原生开发框架 —— .NET Aspire,它提供了如下 3 个方面的能力,来帮助我们使用 .NET 开发分层、云就绪的可观测、本地与生产环境一致的分布式云原生应用程序。
228 0
|
5天前
|
人工智能 自然语言处理 算法
分享几个.NET开源的AI和LLM相关项目框架
分享几个.NET开源的AI和LLM相关项目框架
|
5天前
|
开发框架 网络协议 .NET
深入.net框架
深入.net框架
14 0
|
5天前
|
算法 BI API
C#/.NET/.NET Core优秀项目和框架2024年1月简报
C#/.NET/.NET Core优秀项目和框架2024年1月简报
|
5天前
|
PHP Windows
php扩展com_dndnet(PHP与.NET框架进行交互)
php扩展com_dndnet(PHP与.NET框架进行交互)
php扩展com_dndnet(PHP与.NET框架进行交互)
|
5天前
|
开发框架 前端开发 JavaScript
一款基于.NET Core的快速开发框架、支持多种前端UI、内置代码生成器
一款基于.NET Core的快速开发框架、支持多种前端UI、内置代码生成器
107 0
|
5天前
|
SQL JavaScript NoSQL
3个.NET开源简单易用的任务调度框架
3个.NET开源简单易用的任务调度框架
124 0
|
5天前
|
数据采集 开发框架 JavaScript
C#/.NET/.NET Core优秀项目和框架2023年12月简报
C#/.NET/.NET Core优秀项目和框架2023年12月简报