asp.net mvc应用架构的思考--Unity的应用及三层代码

简介: 最近要做一个项目,和国外的架构师聊过之后。对方提到了他准备采用asp.net mvc, jquery, Unity 等技术来代替老的技术: 如asp.net web form. 他请我帮他想一些关于架构的东西。

 

最近要做一个项目,和国外的架构师聊过之后。对方提到了他准备采用asp.net mvc, jquery, Unity 等技术来代替老的技术: 如asp.net web form. 他请我帮他想一些关于架构的东西。一直以来,关于asp.net mvc应用的架构,有一些想法。正好借这个机会写出来。资深的人士可能已经知道了,就当是复习吧。欢迎发表意见。指出不足。

Unity的应用

Unity出来已经有几年了。早几年的1.2版就可以实现这里所说的功能。目前最新稳定版是2.1。正在开发的3.0也许会给我们带来更强大的功能。这里写的是如何利用Unity实现aop, 降低代码的耦合程度。高层的代码依赖于一个抽象的接口,具体实现的代码是由Unity容器里的具体实现类来完成的。因此Unity容器负责创建具体实现类的实例,将其映射到抽象接口上。这样做是为了降低代码的耦合度。另外我们的应用程序里面有很多公共的部分,如logging, Exception处理,数据库事务处理等都可以通过aop的方式来实现。试想每一个商业方法里都得写调用logging, Exception处理的代码,我们开发人员一定会累的够呛。通过Unity实现了aop,将这些公共的代码从每一个商业方法里抽取出来,让我们的代码看上去更清爽。

用Unity降低代码的耦合度

为了降低代码的耦合度,需要让高层代码依赖于一个抽象的接口。然后具体的实现类就实现这个抽象接口,在Unity容器里注册抽象接口到实现类的映射。即指定了抽象接口映射到某个具体实现类。

具体的实现代码如下:

      <register type="BusinessLogic.IBLLUserForm, BusinessLogic" mapTo="BusinessLogic.BLLUserForm, BusinessLogic">
      </register>
      <register type="BusinessLogic.IBLLUserMenu, BusinessLogic" mapTo="BusinessLogic.BLLUserMenu, BusinessLogic">
      </register>

这里“BusinessLogic"是名称空间。IBLLUserForm和IBLLUserMenu是抽象接口。其名称空间是BusinessLogic。大家可以发现,具体实现类BLLUserForm和BLLUserMenu也是在BusinessLogic名称空间里,这个并不重要,抽象接口和具体实现类可以在同一个名称空间也可以不同名称空间。关键的是抽象类与具体实现类的映射是通过Unity容器来管理的。通过register这个方式向Unity容器表明这个抽象接口和具体实现类的映射关系。这样的映射关系可以在需要的时候改变,比如将来因业务需求出现新的情况,需要一个新的具体实现类叫BLLUserForm_blabla.那么可以用BLLUserForm_blabla代替原来的BLLUserForm, 就只需要改一下配置文件就可以了。原来的代码甚至不用重新编译。试想不用Unity,那么高层代码必须得依赖于具体实现类,因为,即使有抽象接口,抽象接口本身是无法直接创建实例的。高层代码必须得用new操作符来创建一个具体实现类的实例,这样就产生了对具体实现类的直接依赖。这样代码的耦合度就变高了。Unity是一个容器,是一个制造对象的工厂,你需要多少对象,它就制造多少对象。同时能管理这些抽象接口与具体实现类的映射关系。这就是人们常说的依赖于抽象的好处。

    现实当中,也许会出现抽象接口IBLLUserForm等也得改的情况,这种情况的出现是由于OOA/OOD做的不到位,没有正确地发现抽象。得重新审视当时做的分析和设计。

用Unity实现aop,让代码清爽

商业方法中访问数据库,做业务处理难免会遇到未预见的问题,当出现问题时,不希望将此问题信息直接抛给用户,而是希望将问题的详细技术信息写入log。这是一个公用的做法。每一个商业方法都来写try catch,再调用做logging的类来写log。这样就会出现很多的重复代码。好在Unity可以帮我们省去这些重复代码。看看如何做:

这是代码1, 实现Microsoft.Practices.Unity.InterceptionExtension.ICallHandler接口。实现Invoke方法。"var retvalue = getNext()(input, getNext);"是调用被拦截的方法。if (retvalue.Exception != null) 判断retvalue.Exception是判断被拦截方法是否有Exception。如果有就将Exception写log。

using System;
using System.Data;
using System.Data.Common;
using System.Collections.Generic;
using Microsoft.Practices.Unity.InterceptionExtension;
using DataAccessCommon;
using CommonUtil;

namespace BusinessLogic
{
    public class MyDBHandler : ICallHandler
    {
        private int iOrder = 0;

        public IMethodReturn Invoke(IMethodInvocation input, GetNextHandlerDelegate getNext)
        {
            var retvalue = getNext()(input, getNext); // call the intercepting method
            if (retvalue.Exception != null)
            {
                SysLog.GetInstance().LogError(retvalue.Exception);
            }
            return retvalue;
        }

        public int Order
        {
            get
            {
                return iOrder;
            }
            set
            {
                iOrder = value;
            }
        }
    }
}

 这是代码2, 是一个handler attribute, 这个attribute用来标记是否要Unity进行拦截。所有需要拦截的抽象接口都要加上这个attribute. 在下面代码4配置文件里的拦截policy会看抽象接口上是否有这个attribute,有的话就会进行拦截。

using System;
using System.Collections.Generic;
using Microsoft.Practices.Unity;
using Microsoft.Practices.Unity.InterceptionExtension;
namespace BusinessLogic
{
    public class MyDBHandlerAttribute : HandlerAttribute
    {
        public override ICallHandler CreateHandler(IUnityContainer container)
        {
            return new MyDBHandler();
        }
    }
}

 代码3, 这是一个很简单的辅助类,用来帮助在代码4的拦截配置里指定一个自定义attribute的类型。

using System;
using System.Collections.Generic;
using Microsoft.Practices.Unity;
using Microsoft.Practices.Unity.InterceptionExtension;
namespace BusinessLogic
{
    public class GetTypeConverter : System.ComponentModel.TypeConverter
    {
        public override object ConvertFrom(System.ComponentModel.ITypeDescriptorContext context,
            System.Globalization.CultureInfo culture,
            object value)
        {
            return Type.GetType(value.ToString());
        }
    }
}

 代码4, 这是一个拦截配置,其关键点在callHandler和matchingRule上。callHandler指定了用BusinessLogic.MyDBHandler类型来进行拦截处理。MyDBHandler已经在代码1里定义好(见上面)。matchingRule,这里用的是CustomAttributeMatchingRule,即自定义的Attribute作为拦截条件。CustomAttributeMatchingRule要求两个构造函数参数,一个是Type, 一个是否使用继承的类型. 在指定Type时,用的是一个字符串:"BusinessLogic.MyDBHandlerAttribute, BusinessLogic", 所以需要typeConverter将其转换成Type。

      <interception>
        <policy name="mypolicy">
          <callHandler name="myHandler1" type="BusinessLogic.MyDBHandler, BusinessLogic"></callHandler>
          <matchingRule name="myrule" type="CustomAttributeMatchingRule">
            <constructor>
              <param name="attributeType" type="System.Type, mscorlib">
                <value value="BusinessLogic.MyDBHandlerAttribute, BusinessLogic" typeConverter="BusinessLogic.GetTypeConverter, BusinessLogic" />
              </param>
              <param name="inherited" type="bool">
                <value value="true" />
              </param>
            </constructor>
          </matchingRule>
        </policy>
      </interception>

代码5, 相应地在类型注册当中也加入拦截器和policyinjection的设定。这里用的是InterfaceInterceptor。如下:

      <register type="BusinessLogic.IBLLUserForm, BusinessLogic" mapTo="BusinessLogic.BLLUserForm, BusinessLogic">
        <interceptor name="myinterceptor" type="InterfaceInterceptor" isDefaultForType="true" />
        <policyInjection />
      </register>
      <register type="BusinessLogic.IBLLUserMenu, BusinessLogic" mapTo="BusinessLogic.BLLUserMenu, BusinessLogic">
        <interceptor name="myinterceptor" type="InterfaceInterceptor" isDefaultForType="true" />
        <policyInjection />
      </register>

 关于数据库的事务,如果你的数据库任务相对比较简单(比如仅CRUD),数据库事务size比较小,可以用System.Transactions.TransactionScope,那么可以下面的一段代码实现超简单aop方式的事务管理:

代码6, 这是替换代码1的一个片段:

 

                using (TransactionScope ts = new TransactionScope())
                {
                    var retvalue = getNext().Invoke(input, getNext);
                    if (retvalue.Exception != null)
                    {
                        SysLog.GetInstance().LogError(retvalue.Exception);
                    }
                    else
                    {
                        ts.Complete();
                    }
                    return retvalue
                }

 

var retvalue = getNext().Invoke(input, getNext);这句是调用被拦截的方法。即我们的商业方法。我们可以在拦截之前,即此语句之前做一些logging的工作,还可以在此语句之后做一些logging的工作。视具体要求而定。有了Exception处理,和相应的logging,还有数据库事务的处理。那么商务逻辑的类和相应的数据库访问的类就变得相对简单了。原来需要显式地创建事务对象,现在不需要了,现在的商务方法默认就是打开了事务管理的。在商务方法里的所有数据库操作都会被看成是同一个数据库事务。在商务方法结束以后会自动进行数据库事务的提交。

三层代码

代码7 抽象接口的代码,其引用了自定义的attribute "MyDBHandler",这个attribute会带来aop方式的Exception, logging, 和事务处理。

using System;
namespace BusinessLogic
{
    [MyDBHandler]
    public interface IBLLUserMenu
    {
        int AddUserMenu(DataEntity.UserMenu usermenu);
        int DeleteUserMenu(DataEntity.UserMenu usermenu);
        DataEntity.UserMenu FindAUserMenu(DataEntity.UserMenu usermenu);
        System.Collections.Generic.List<DataEntity.UserMenu> GetAllUserMenu();
        int SelectCountUserMenu();
        int SelectCountWhereClauseUserMenu(DataEntity.UserMenu usermenu);
        System.Collections.Generic.List<DataEntity.UserMenu> SelectGroupByPrimaryKeyUserMenu();
        System.Collections.Generic.List<DataEntity.UserMenu> SelectMenusByApplicationID(DataEntity.UserMenu usermenu);
        System.Collections.Generic.List<DataEntity.UserMenu> SelectOrderByPrimaryKeyUserMenu();
        System.Collections.Generic.List<DataEntity.UserMenu> SelectTopUserMenu();
        int UpdateUserMenu(DataEntity.UserMenu usermenu);
        object CONNECTION { get; set; }
        DataEntity.UserMenu USERMENU { get; set; }
        System.Collections.Generic.List<DataEntity.UserMenu> USERMENU_LIST { get; set; }
    }
}

 

代码8 商务类及其方法, 经过aop处理之后,每一个方法都有Exception的处理, logging, 和事务控制。

using System;
using System.Collections.Generic;
using System.Data;
using DataEntity;
using DataAccess;
using DataAccessCommon;
using CommonUtil;

namespace BusinessLogic
{
    internal class BLLUserMenu : BusinessLogic.IBLLUserMenu
    {
        private readonly DataAccess.DALUserMenu dal = new DataAccess.DALUserMenu();
        private object conn = null;
        private UserMenu usermenu;
        private List<UserMenu> usermenus;

        public object CONNECTION
        {
            get
            {
                return conn;
            }
            set
            {
                conn = value;
            }
        }
        
        public UserMenu USERMENU
        {
            get
            {
                return usermenu;
            }
            set
            {
                usermenu = value;
            }
        }
        
        public List<UserMenu> USERMENU_LIST
        {
            get
            {
                return usermenus;
            }
            set
            {
                usermenus = value;
            }
        }
        
        #region business logic method

        public int DeleteUserMenu(UserMenu usermenu)
        {
            return dal.DeleteUserMenu(conn,usermenu);
        }

        public int UpdateUserMenu(UserMenu usermenu)
        {
            return dal.UpdateUserMenu(conn,usermenu);
        }

        public int AddUserMenu(UserMenu usermenu)
        {
            return dal.AddUserMenu(conn,usermenu);
        }

        public List<UserMenu> GetAllUserMenu()
        {
            return dal.GetAllUserMenu(conn);
        }

        public UserMenu FindAUserMenu(UserMenu usermenu)
        {
            return dal.FindAUserMenu(conn,usermenu);
        }

        public System.Int32 SelectCountUserMenu()
        {
            return dal.SelectCountUserMenu(conn);
        }

        public System.Int32 SelectCountWhereClauseUserMenu(UserMenu usermenu)
        {
            return dal.SelectCountWhereClauseUserMenu(conn,usermenu);
        }

        public List<UserMenu> SelectTopUserMenu()
        {
            return dal.SelectTopUserMenu(conn);
        }

        public List<UserMenu> SelectOrderByPrimaryKeyUserMenu()
        {
            return dal.SelectOrderByPrimaryKeyUserMenu(conn);
        }

        public List<UserMenu> SelectGroupByPrimaryKeyUserMenu()
        {
            return dal.SelectGroupByPrimaryKeyUserMenu(conn);
        }

        public List<UserMenu> SelectMenusByApplicationID(UserMenu usermenu)
        {
            return dal.SelectMenusByApplicationID(conn, usermenu);
        }
        #endregion
    }
}

代码9 有多个数据库操作的商务方法, 上面的商务类及其方法是比较简单的,所以一个商务方法里只有一个数据库操作. 很多情况下是有多个的,甚至是比较复杂的。如下面的例子:

        public bool MoveUpItem(UserMenuItem usermenuitem)
        {
            usermenuitem = dal.FindAUserMenuItem(conn, usermenuitem);
            UserMenuItem previousMenuItem = dal.SelectPreviousMenuItem(conn, usermenuitem);
            int iTempOrdinal = usermenuitem.ORDINAL;
            usermenuitem.ORDINAL = previousMenuItem.ORDINAL;
            previousMenuItem.ORDINAL = iTempOrdinal;
            dal.UpdateUserMenuItem(conn, usermenuitem);
            dal.UpdateUserMenuItem(conn, previousMenuItem);
            return true;
        }

        public bool MoveDownItem(UserMenuItem usermenuitem)
        {
            usermenuitem = dal.FindAUserMenuItem(conn, usermenuitem);
            UserMenuItem nextMenuItem = dal.SelectNextMenuItem(conn, usermenuitem);
            int iTempOrdinal = usermenuitem.ORDINAL;
            usermenuitem.ORDINAL = nextMenuItem.ORDINAL;
            nextMenuItem.ORDINAL = iTempOrdinal;
            dal.UpdateUserMenuItem(conn, usermenuitem);
            dal.UpdateUserMenuItem(conn, nextMenuItem);
            return true;
        }

 代码10, 数据访问的代码, 基本都是这个模式, 构造参数,CRUD的sql语句,然后调用db helper执行。这些数据访问的方法返回类型是这几种:int, 单个商务实体,商务实体的列表,bool. int 通常表示影响到的记录数,也可以是某个整形字段的值, 单个商务实体代表数据库里一条记录。商务实体的列表代表数据库里多个符合条件的记录。这些都转换成了商务实体类。

using System;
using System.Collections.Generic;
using System.Data;
using DataEntity;
using DataAccessCommon;

namespace DataAccess
{
    public class DALUserForm
    {
        #region data access methods

        public int DeleteUserForm(Object conn, UserForm userform)
        {
            List<MyStaticDBHelper.MyDBParameter> paras = new List<MyStaticDBHelper.MyDBParameter> {
                new MyStaticDBHelper.MyDBParameter("@FormID", DbType.Int32, userform.FORMID)
            };

            string strSQL = "DELETE FROM [UserForm]   WHERE   [FormID] = @FormID";

            int result = 0;
            result = MyStaticDBHelper.ExecuteNonQuery(conn, System.Data.CommandType.Text, strSQL, paras);
            return result;

        }

        public int UpdateUserForm(Object conn, UserForm userform)
        {
            List<MyStaticDBHelper.MyDBParameter> paras = new List<MyStaticDBHelper.MyDBParameter> {
                new MyStaticDBHelper.MyDBParameter("@FormName", DbType.String, userform.FORMNAME),
                new MyStaticDBHelper.MyDBParameter("@TableID", DbType.Int32, userform.TABLEID),
                new MyStaticDBHelper.MyDBParameter("@SingleOrList", DbType.Boolean, userform.SINGLEORLIST),
                new MyStaticDBHelper.MyDBParameter("@WithCRUD", DbType.Boolean, userform.WITHCRUD),
                new MyStaticDBHelper.MyDBParameter("@ApplicationID", DbType.Int32, userform.APPLICATIONID),
                new MyStaticDBHelper.MyDBParameter("@FormID", DbType.Int32, userform.FORMID)
            };

            string strSQL = "UPDATE [UserForm] SET   [FormName] = @FormName,  [TableID] = @TableID,  [SingleOrList] = @SingleOrList,  [WithCRUD] = @WithCRUD,  [ApplicationID] = @ApplicationID  WHERE   [FormID] = @FormID";

            int result = 0;
            result = MyStaticDBHelper.ExecuteNonQuery(conn, System.Data.CommandType.Text, strSQL, paras);
            return result;

        }

        public int AddUserForm(Object conn, UserForm userform)
        {
            List<MyStaticDBHelper.MyDBParameter> paras = new List<MyStaticDBHelper.MyDBParameter> {
                new MyStaticDBHelper.MyDBParameter("@FormName", DbType.String, userform.FORMNAME),
                new MyStaticDBHelper.MyDBParameter("@TableID", DbType.Int32, userform.TABLEID),
                new MyStaticDBHelper.MyDBParameter("@SingleOrList", DbType.Boolean, userform.SINGLEORLIST),
                new MyStaticDBHelper.MyDBParameter("@WithCRUD", DbType.Boolean, userform.WITHCRUD),
                new MyStaticDBHelper.MyDBParameter("@ApplicationID", DbType.Int32, userform.APPLICATIONID),
                new MyStaticDBHelper.MyDBParameter("@FormID", DbType.Int32, userform.FORMID)
            };

            string strSQL = "INSERT INTO [UserForm] (  [FormName] ,  [TableID] ,  [SingleOrList] ,  [WithCRUD] ,  [ApplicationID]  ) VALUES( @FormName, @TableID, @SingleOrList, @WithCRUD, @ApplicationID );  SELECT SCOPE_IDENTITY() as [FormID]";

            int result = 0;
            DataSet ds = null;
            ds = MyStaticDBHelper.ExecuteDataset(conn, System.Data.CommandType.Text, strSQL, paras);
            if (ds.Tables.Count > 0 && ds.Tables[0].Rows.Count > 0){
                userform.FORMID = Convert.ToInt32(ds.Tables[0].Rows[0][0]);
                result = 1;
            }
            return result;

        }

        public List<UserForm> GetAllUserForm(Object conn)
        {

            string strSQL = "SELECT * FROM [UserForm] ";

            DataSet ds = null;
            ds = MyStaticDBHelper.ExecuteDataset(conn, System.Data.CommandType.Text, strSQL);

            return DataMapper.MapDataTableToObjectList<UserForm>(ds.Tables[0]);

        }

        public UserForm FindAUserForm(Object conn, UserForm userform)
        {
            List<MyStaticDBHelper.MyDBParameter> paras = new List<MyStaticDBHelper.MyDBParameter> {
                new MyStaticDBHelper.MyDBParameter("@FormID", DbType.Int32, userform.FORMID)
            };

            string strSQL = "SELECT * FROM [UserForm]   WHERE   [FormID] = @FormID";

            DataSet ds = null;
            ds = MyStaticDBHelper.ExecuteDataset(conn, System.Data.CommandType.Text, strSQL, paras);

            return DataMapper.MapDataTableToSingleRow<UserForm>(ds.Tables[0]);

        }
        //省略了大部分
#endregion
    }
}

代码11  商务实体类的代码

using System;
using System.Collections.Generic;

namespace DataEntity
{
    public class UserMenu
    {
        #region private members
        private int _iMenuID;
        private string _strMenuName;
        private int _iApplicationID;
        private int _iMenuStyle;
        private int _iScheme;
        #endregion
        
        #region Properties
        public int MENUID
        {
            get
            {
                return _iMenuID;
            }
            set
            {
                _iMenuID = value;
            }
        }
        public string MENUNAME
        {
            get
            {
                return _strMenuName;
            }
            set
            {
                _strMenuName = value;
            }
        }
        public int APPLICATIONID
        {
            get
            {
                return _iApplicationID;
            }
            set
            {
                _iApplicationID = value;
            }
        }
        public int MENUSTYLE
        {
            get
            {
                return _iMenuStyle;
            }
            set
            {
                _iMenuStyle = value;
            }
        }
        public int SCHEME
        {
            get
            {
                return _iScheme;
            }
            set
            {
                _iScheme = value;
            }
        }
        #endregion
    }
}

 

如果不用TransactionScope

严格的说,TransactionScope有些局限性。人们已经发现不少的TransactionScope的局限(baidu, google搜搜就有好多)。不管是否分布式事务,只要是规模比较大,复杂度比较高,最好都不要用TransactionScope。数据库事务规模小,复杂度低,用TransactionScope还是不错的。一旦规模变大,复杂度变高,TransactionScope带来的问题会很棘手。最后就只有弃用TransactionScope。问题也来了,不用TransactionScope那用什么呢。TransactionScope还是带来编程的一些方便,写一个using子句就可以了,它可以管理非分布式数据库事务,也可以管理分布式数据库事务。大家可以去看园友artech的一篇文章: http://www.cnblogs.com/artech/archive/2012/01/05/custom-transaction-scope.html, 他们提出来一个类似于TransactionScope用法的类。不过他们提出的类暂时不支持分布式事务。如果是非分布式的,那么可以借用。另外需要说的是artech在这篇文章里贴的代码证明一例TransactionScope的问题。当插入记录到了100000条,一次性提交事务,TransactionScope居然挂了。这还只是单数据库,不知道更复杂的,更大的会如何。希望大家集思广益,早点找出一个完美代替TransactionScope的解决方案。

 

目录
相关文章
|
存储 Shell Linux
快速上手基于 BaGet 的脚本自动化构建 .net 应用打包
本文介绍了如何使用脚本自动化构建 `.net` 应用的 `nuget` 包并推送到指定服务仓库。首先概述了 `BaGet`——一个开源、轻量级且高性能的 `NuGet` 服务器,支持多种存储后端及配置选项。接着详细描述了 `BaGet` 的安装、配置及使用方法,并提供了 `PowerShell` 和 `Bash` 脚本实例,用于自动化推送 `.nupkg` 文件。最后总结了 `BaGet` 的优势及其在实际部署中的便捷性。
915 10
|
7月前
|
人机交互 开发工具 vr&ar
使用Unity引擎开发Rokid主机应用的模型交互操作
本文介绍如何使用Unity引擎结合Rokid OpenXR Plugin开发空间计算应用,实现射线交互、模型操作等功能。涵盖环境配置、Demo导入、UI搭建与脚本编写,助力开发者快速构建AR交互应用。
|
C# Android开发 iOS开发
2025年全面的.NET跨平台应用框架推荐
2025年全面的.NET跨平台应用框架推荐
760 23
|
算法 Java 测试技术
使用 BenchmarkDotNet 对 .NET 代码进行性能基准测试
使用 BenchmarkDotNet 对 .NET 代码进行性能基准测试
429 13
|
开发框架 .NET PHP
ASP.NET Web Pages - 添加 Razor 代码
ASP.NET Web Pages 使用 Razor 标记添加服务器端代码,支持 C# 和 Visual Basic。Razor 语法简洁易学,类似于 ASP 和 PHP。例如,在网页中加入 `@DateTime.Now` 可以实时显示当前时间。
|
敏捷开发 缓存 中间件
.NET技术的高效开发模式,涵盖面向对象编程、良好架构设计及高效代码编写与管理三大关键要素
本文深入探讨了.NET技术的高效开发模式,涵盖面向对象编程、良好架构设计及高效代码编写与管理三大关键要素,并通过企业级应用和Web应用开发的实践案例,展示了如何在实际项目中应用这些模式,旨在为开发者提供有益的参考和指导。
189 3
|
开发框架 监控 .NET
【Azure App Service】部署在App Service上的.NET应用内存消耗不能超过2GB的情况分析
x64 dotnet runtime is not installed on the app service by default. Since we had the app service running in x64, it was proxying the request to a 32 bit dotnet process which was throwing an OutOfMemoryException with requests >100MB. It worked on the IaaS servers because we had the x64 runtime install
382 5
|
JSON 算法 安全
JWT Bearer 认证在 .NET Core 中的应用
【10月更文挑战第30天】JWT(JSON Web Token)是一种开放标准,用于在各方之间安全传输信息。它由头部、载荷和签名三部分组成,用于在用户和服务器之间传递声明。JWT Bearer 认证是一种基于令牌的认证方式,客户端在请求头中包含 JWT 令牌,服务器验证令牌的有效性后授权用户访问资源。在 .NET Core 中,通过安装 `Microsoft.AspNetCore.Authentication.JwtBearer` 包并配置认证服务,可以实现 JWT Bearer 认证。具体步骤包括安装 NuGet 包、配置认证服务、启用认证中间件、生成 JWT 令牌以及在控制器中使用认证信息
626 2
|
数据采集 JSON API
.NET 3.5 中 HttpWebRequest 的核心用法及应用
【9月更文挑战第7天】在.NET 3.5环境下,HttpWebRequest 类是处理HTTP请求的一个核心组件,它封装了HTTP协议的细节,使得开发者可以方便地发送HTTP请求并接收响应。本文将详细介绍HttpWebRequest的核心用法及其实战应用。
847 6
|
前端开发 JavaScript C#
CodeMaid:一款基于.NET开发的Visual Studio代码简化和整理实用插件
CodeMaid:一款基于.NET开发的Visual Studio代码简化和整理实用插件
486 0