主頁 > .NET開發 > 一文了解.Net Core 3.1 Web API基礎知識

一文了解.Net Core 3.1 Web API基礎知識

2020-09-28 17:54:28 .NET開發

一、前言

  隨著近幾年前后端分離、微服務等模式的興起,.Net Core也似有如火如荼之勢 ,自16年發布第一個版本到19年底的3.1 LTS版本,以及將發布的.NET 5,.NET Core一路更迭,在部署和開發工具上也都支持了跨平臺應用,一直對.Net Core有所關注,但未涉及太多實際應用,經過一番學習和了解后,于是分享出來,本文主要以.Net Core Web API為例,講述.Net Core的基本應用及注意事項,對于想通過WebAPI搭建介面應用的開發者,應該能提供一個系統的輪廓和認識,同時和更多的.Net Core開發者交流互動,探本勘誤,加強對知識的理解,并幫助更多的人,本文以貼近基本的實際操作為主,部分概念或基礎步驟不再贅述,文中如有疏漏,還望不吝斧正,

二、Swagger除錯Web API

開發環境:Visual Studio 2019

為解決前后端苦于介面檔案與實際不一致、維護和更新檔案的耗時費力等問題,swagger應運而生,同時也解決了介面測驗問題,話不多說,直接說明應用步驟,

  1. 新建一個ASP.NET Core Web API應用程式,版本選擇.ASP.NET Core 3.1;
  2. 通過Nuget安裝包:Swashbuckle.AspNetCore,當前示例版本5.5.0;
  3. 在Startup類的ConfigureServices方法內添加以下注入代碼:
    services.AddSwaggerGen(c =>
                {
                    c.SwaggerDoc("v1", new OpenApiInfo
                    {
                        Title = "My API",
                        Version = "v1",
                        Description = "API檔案描述",
                        Contact = new OpenApiContact
                        {
                            Email = "[email protected]",
                            Name = "測驗專案",
                            //Url = new Uri("http://t.abc.com/")
                        },
                        License = new OpenApiLicense
                        {
                            Name = "BROOKE許可證",
                            //Url = new Uri("http://t.abc.com/")
                        }
                    });              
    
                });

    Startup類的Configure方法添加如下代碼:

    //配置Swagger
                app.UseSwagger();            
                app.UseSwaggerUI(c =>
                {
                    c.SwaggerEndpoint("/swagger/v1/swagger.json", "My API V1");
                    c.RoutePrefix = "api";// 如果設為空,訪問路徑就是根域名/index.html,設定為空,表示直接在根域名訪問;想換一個路徑,直接寫名字即可,比如直接寫c.RoutePrefix = "swagger"; 則訪問路徑為 根域名/swagger/index.html
    
                });

    Ctrl+F5進入瀏覽,按上述配置修改路徑為:http://localhost:***/api/index.html,即可看到Swagger頁面:

    然而到這里還沒完,相關介面的注釋說明我們看不到,通過配置XML檔案的方式繼續調整代碼如下,新增代碼見加粗部分:
    services.AddSwaggerGen(c =>
                {
                    c.SwaggerDoc("v1", new OpenApiInfo
                    {
                        Title = "My API",
                        Version = "v1",
                        Description = "API檔案描述",
                        Contact = new OpenApiContact
                        {
                            Email = "[email protected]",
                            Name = "測驗專案",
                            //Url = new Uri("http://t.abc.com/")
                        },
                        License = new OpenApiLicense
                        {
                            Name = "BROOKE許可證",
                            //Url = new Uri("http://t.abc.com/")
                        }
                    });
                 
                    var xmlFile = $"{Assembly.GetExecutingAssembly().GetName().Name}.xml";
                    var xmlPath = Path.Combine(AppContext.BaseDirectory, xmlFile);
                    c.IncludeXmlComments(xmlPath);
                });

    上述代碼通過反射生成與Web API專案相匹配的XML檔案名,AppContext.BaseDirectory屬性用于構造 XML 檔案的路徑,關于OpenApiInfo內的配置引數用于檔案的一些描述,在此不作過多說明,
    然后右鍵Web API專案、屬性、生成,配置XML檔案的輸出路徑,以及取消不必要的XML注釋警告提醒(增加1591):



    這樣,我們以三斜杠(///)方式給類方法屬性等相關代碼添加注釋后,重繪Swagger頁面,即可看到注釋說明,
    如果不想將XML檔案輸出為debug下的目錄,譬如想要放在專案根目錄(但不要修改成磁盤絕對路徑),可調整相關代碼如下,xml檔案的名字也可以改成自己想要的:
    var basePath = Path.GetDirectoryName(typeof(Program).Assembly.Location);//獲取應用程式所在目錄
    var xmlPath = Path.Combine(basePath, "CoreAPI_Demo.xml");
    c.IncludeXmlComments(xmlPath, true);

    同時,調整專案生成的XML檔案檔案路徑為:..\CoreAPI_Demo\CoreAPI_Demo.xml

  4. 隱藏相關介面
    對于不想暴漏給Swagger展示的介面,我們可以給相關Controller或Action頭加上:[ApiExplorerSettings(IgnoreApi = true)]

  5. 調整系統默認輸出路徑
    專案啟動后,默認會訪問自帶的weatherforecast,如果想調整為其他路徑,譬如打開后直接訪問Swagger檔案,那么調整Properties目錄下的launchSettings.json檔案,修改launchUrl值為api(前述配置的RoutePrefix值):
    {
      "$schema": "http://json.schemastore.org/launchsettings.json",
      "iisSettings": {
        "windowsAuthentication": false,
        "anonymousAuthentication": true,
        "iisExpress": {
          "applicationUrl": "http://localhost:7864",
          "sslPort": 0
        }
      },
      "profiles": {
        "IIS Express": {
          "commandName": "IISExpress",
          "launchBrowser": true,
          "launchUrl": "api",
          "environmentVariables": {
            "ASPNETCORE_ENVIRONMENT": "Development"
          }
        },
        "CoreApi_Demo": {
          "commandName": "Project",
          "launchBrowser": true,
          "launchUrl": "api",
          "applicationUrl": "http://localhost:5000",
          "environmentVariables": {
            "ASPNETCORE_ENVIRONMENT": "Development"
          }
        }
      }
    }

     

三、組態檔

以讀取appsettings.json檔案為例,當然你也定義其他名稱的.json檔案進行讀取,讀取方式一致,該檔案類似于Web.config檔案,為方便示例,定義appsettings.json檔案內容如下:

{
  "ConnString": "Data Source=(local);Initial Catalog=Demo;Persist Security Info=True;User ID=DemoUser;Password=123456;MultipleActiveResultSets=True;",
  "ConnectionStrings": {
    "MySQLConnection": "server=127.0.0.1;database=mydemo;uid=root;pwd=123456;charset=utf8;SslMode=None;"
  },
  "SystemConfig": {
    "UploadFile": "/Files",
    "Domain": "http://localhost:7864"
  },
  "JwtTokenConfig": {
    "Secret": "fcbfc8df1ee52ba127ab",
    "Issuer": "abc.com",
    "Audience": "Brooke.WebApi",
    "AccessExpiration": 30,
    "RefreshExpiration": 60
  },
  "Logging": {
    "LogLevel": {
      "Default": "Information",
      "Microsoft": "Warning",
      "Microsoft.Hosting.Lifetime": "Information"
    }
  },
  "AllowedHosts": "*"
}

 

1、組態檔的基本讀取

public class Startup
    {
        public Startup(IConfiguration configuration)
        {
            Configuration = configuration;
        }

        public IConfiguration Configuration { get; }

        // This method gets called by the runtime. Use this method to add services to the container.
        public void ConfigureServices(IServiceCollection services)
        {
            services.AddControllers();

            //讀取方式一
            var ConnString = Configuration["ConnString"];            
            var MySQLConnection = Configuration.GetSection("ConnectionStrings")["MySQLConnection"];
            var UploadPath = Configuration.GetSection("SystemConfig")["UploadPath"];
            var LogDefault = Configuration.GetSection("Logging").GetSection("LogLevel")["Default"];
            
            //讀取方式二
            var ConnString2 = Configuration["ConnString"];
            var MySQLConnection2 = Configuration["ConnectionStrings:MySQLConnection"];
            var UploadPath2 = Configuration["SystemConfig:UploadPath"];
            var LogDefault2 = Configuration["Logging:LogLevel:Default"];
            
        }    
        
    }

以上介紹了2種讀取配置資訊的方式,如果要在Controller內使用,類似地,進行注入并呼叫如下:

public class ValuesController : ControllerBase
    {
        private IConfiguration _configuration;

        public ValuesController(IConfiguration configuration)
        {
            _configuration = configuration;
        }

        // GET: api/<ValuesController>
        [HttpGet]
        public IEnumerable<string> Get()
        {
            var ConnString = _configuration["ConnString"];
            var MySQLConnection = _configuration.GetSection("ConnectionStrings")["MySQLConnection"];
            var UploadPath = _configuration.GetSection("SystemConfig")["UploadPath"];
            var LogDefault = _configuration.GetSection("Logging").GetSection("LogLevel")["Default"];
            return new string[] { "value1", "value2" };
        }
    }

 

2、讀取組態檔到自定義物件

以SystemConfig節點為例,定義類如下:

public class SystemConfig
    {
        public string UploadPath { get; set; }
        public string Domain { get; set; }       
  
    }

調整代碼如下:

public class Startup
    {
        public Startup(IConfiguration configuration)
        {
            Configuration = configuration;
        }

        public IConfiguration Configuration { get; }

        // This method gets called by the runtime. Use this method to add services to the container.
        public void ConfigureServices(IServiceCollection services)
        {
            services.AddControllers();

            services.Configure<SystemConfig>(Configuration.GetSection("SystemConfig"));
        }    
        
    }

 然后Controller內進行注入呼叫:

[Route("api/[controller]/[action]")]
    [ApiController]
    public class ValuesController : ControllerBase
    {        
        private SystemConfig _sysConfig;
        public ValuesController(IOptions<SystemConfig> sysConfig)
        {           
            _sysConfig = sysConfig.Value;
        }        

        [HttpGet]
        public IEnumerable<string> GetSetting()
        {
            var UploadPath = _sysConfig.UploadPath;
            var Domain = _sysConfig.Domain;
            return new string[] { "value1", "value2" };
        }
    }

3、系結到靜態類方式讀取

定義相關靜態類如下:

  public static class MySettings
    {
        public static SystemConfig Setting { get; set; } = new SystemConfig();
    }    

調整Startup類建構式如下:

public Startup(IConfiguration configuration, IWebHostEnvironment env)
        {            
            var builder = new ConfigurationBuilder()
              .SetBasePath(env.ContentRootPath)
              .AddJsonFile("appsettings.json", optional: true, reloadOnChange: true);

            Configuration = builder.Build();
            //Configuration = configuration;

            configuration.GetSection("SystemConfig").Bind(MySettings.Setting);//系結靜態配置類           
        }       

接下來,諸如直接使用:MySettings.Setting.UploadPath 即可呼叫,

 

四、檔案上傳

介面一般少不了檔案上傳,相比.net framework框架下webapi通過byte陣列物件等復雜方式進行檔案上傳,.Net Core WebApi有了很大變化,其定義了新的IFormFile物件來接收上傳檔案,直接上Controller代碼: 

后端代碼

[Route("api/[controller]/[action]")]
    [ApiController]
    public class UploadController : ControllerBase
    {
        private readonly IWebHostEnvironment _env;

        public UploadController(IWebHostEnvironment env)
        {
            _env = env;
        }

        public ApiResult UploadFile(List<IFormFile> files)
        {
            ApiResult result = new ApiResult();
       //注:引數files物件去也可以通過換成: var files = Request.Form.Files;來獲取
if (files.Count <= 0) { result.Message = "上傳檔案不能為空"; return result; } #region 上傳 List<string> filenames = new List<string>(); var webRootPath = _env.WebRootPath; var rootFolder = MySettings.Setting.UploadPath; var physicalPath = $"{webRootPath}/{rootFolder}/"; if (!Directory.Exists(physicalPath)) { Directory.CreateDirectory(physicalPath); } foreach (var file in files) { var fileExtension = Path.GetExtension(file.FileName);//獲取檔案格式,拓展名 var saveName = $"{rootFolder}/{Path.GetRandomFileName()}{fileExtension}"; filenames.Add(saveName);//相對路徑 var fileName = webRootPath + saveName; using FileStream fs = System.IO.File.Create(fileName); file.CopyTo(fs); fs.Flush(); } #endregion result.IsSuccess = true; result.Data["files"] = filenames; return result; } }

前端呼叫

接下來通過前端呼叫上述上傳介面,在專案根目錄新建wwwroot目錄(.net core webapi內置目錄 ),添加相關js檔案包,然后新建一個index.html檔案,內容如下:

<!DOCTYPE html>
<html>
<head>
    <meta charset="utf-8" />
    <title></title>
    <style type="text/css">
        
    </style>
    <script src=https://www.cnblogs.com/ang/archive/2020/09/27/"res/scripts/jquery-1.10.2.min.js"></script>
    <script src=https://www.cnblogs.com/ang/archive/2020/09/27/"res/scripts/jquery.form.js"></script>
    <script type="text/javascript">
        //方法1
        function AjaxUploadfile() {
            var upload = $("#files").get(0);
            var files = upload.files;
            var data = https://www.cnblogs.com/ang/archive/2020/09/27/new FormData();
            for (var i = 0; i < files.length; i++) {
                data.append("files", files[i]);
            }

            //此處data的構建也可以換成:var data = https://www.cnblogs.com/ang/archive/2020/09/27/new FormData(document.getElementById("myform"));

            $.ajax({
                type: "POST",
                url: "/api/upload/uploadfile",
                contentType: false,
                processData: false,
                data: data,
                success: function (result) {
                    alert("success");
                    $.each(result.data.files, function (i, filename) {
                        $("#filePanel").append('<p>' + filename + '</p>');
                    });
                },
                error: function () {
                    alert("上傳檔案錯誤");
                }
            });
        }


        //方法2
        function AjaxUploadfile2() {
            $("#myform").ajaxSubmit({
                success: function (result) {
                    if (result.isSuccess) {
                        $.each(result.data.files, function (i, filename) {
                            $("#filePanel").append('<p>' + filename + '</p>');
                        });
                    }
                    else {
                        alert(result.message);
                    }
                }
            });
        }       
       
    </script>   
</head>
<body>
    <form id="myform" method="post" action="/api/upload/uploadfile" enctype="multipart/form-data">
        <input type="file" id="files" name="files" multiple /> <br /><br />
        <input type="button" value=https://www.cnblogs.com/ang/archive/2020/09/27/"FormData Upload" onclick="AjaxUploadfile();" /><br /><br />
        <input type="button" value=https://www.cnblogs.com/ang/archive/2020/09/27/"ajaxSubmit Upload" onclick="AjaxUploadfile2();" /><br /><br />      
        <div id="filePanel"></div>
    </form>

    <script type="text/javascript">

        $(function () {

        });

    </script>
</body>
</html>

上述通過構建FormData和ajaxSubmit兩種方式進行上傳,需要注意的是contentType和processData兩個引數的設定;另外允許一次上傳多個檔案,需設定multipart屬性,


在訪問wwwroot下的靜態檔案之前,必須先在Startup類的Configure方法下進行注冊:

public void Configure(IApplicationBuilder app)
{
    app.UseStaticFiles();//用于訪問wwwroot下的檔案    
} 

 啟動專案,通過訪問路徑:http://localhost:***/index.html,進行上傳測驗,成功后,將在wwwroot下的Files目錄下看到上傳的檔案,

 

五、統一WebApi資料回傳格式

定義統一回傳格式

為了方便前后端使用約定好的資料格式,通常我們會定義統一的資料回傳,其包括是否成功、回傳狀態、具體資料等;為便于說明,定義一個資料回傳類如下:

public class ApiResult
    {
        public bool IsSuccess { get; set; }
        public string Message { get; set; }
        public string Code { get; set; }
        public Dictionary<string, object> Data { get; set; } = new Dictionary<string, object>();
    }

 

這樣,我們將每一個action介面操作封裝為ApiResult格式進行回傳,新建一個ProductController示例如下:

[Produces("application/json")]  
    [Route("api/[controller]")]
    [ApiController]
    public class ProductController : ControllerBase
    {
        [HttpGet]
        public ApiResult Get()
        {
            var result = new ApiResult();

            var rd = new Random();

            result.Data["dataList"] = Enumerable.Range(1, 5).Select(index => new 
            {
                Name = $"商品-{index}",
                Price = rd.Next(100, 9999)
            });           

            result.IsSuccess = true;
            return result;
        }
    }

 

  • Produces:定義資料回傳的方式,給每個Controller打上[Produces("application/json")]標識,即表示以json方式進行資料輸出,
  • ApiController:確保每個Controller有ApiController標識,通常,我們會定義一個基類如:BaseController,其繼承自ControllerBase,并將其打上[ApiController]標識,新建的controller都繼承該類;
  • Route:路由訪問方式,如不喜歡RESTful方式,可加上Action,即:[Route("api/[controller]/[action]")];
  • HTTP 請求:結合前面配置的Swagger,必須確保每個Action都有具體的請求方式,即必須是HttpGet、HttpPost、HttpPut、HttpDelete中的一種,通常情況下,我們使用HttpGet、HttpPost足以,

  如此,即完成的資料回傳的統一,

解決T時間格式

  .Net Core Web Api默認以首字母小寫的類駝峰式命名回傳,但遇到DateTime型別的資料,會回傳T格式時間,如要解決T時間格式,定義一個時間格式轉換類如下:

public class DatetimeJsonConverter : JsonConverter<DateTime>
    {
        public override DateTime Read(ref Utf8JsonReader reader, Type typeToConvert, JsonSerializerOptions options)
        {
            if (reader.TokenType == JsonTokenType.String)
            {
                if (DateTime.TryParse(reader.GetString(), out DateTime date))
                    return date;
            }
            return reader.GetDateTime();
        }

        public override void Write(Utf8JsonWriter writer, DateTime value, JsonSerializerOptions options)
        {
            writer.WriteStringValue(value.ToString("yyyy-MM-dd HH:mm:ss"));
        }
    }

然后在Startup類的ConfigureServices中調整services.AddControllers代碼如下:

services.AddControllers()
                .AddJsonOptions(configure =>
                {
                    configure.JsonSerializerOptions.Converters.Add(new DatetimeJsonConverter());
                });

 

六、模型驗證

模型驗證在ASP.NET MVC已存在,使用方式基本一致,指對向介面提交過來的資料進行引數校驗,包括必填項、資料格式、字符長度、范圍等等,一般的,我們會將POST提交過來的物件定義為一個物體類進行接收,譬如定義一個注冊類如下:

    public class RegisterEntity
    {
        /// <summary>
        /// 手機號
        /// </summary>
        [Display(Name = "手機號")]
        [Required(ErrorMessage = "{0}不能為空")]
        [StringLength(11, ErrorMessage = "{0}最多{1}個字符")]
        public string Mobile { get; set; }

        /// <summary>
        /// 驗證碼
        /// </summary>
        [Display(Name = "驗證碼")]
        [Required(ErrorMessage = "{0}不能為空")]
        [StringLength(6, ErrorMessage = "{0}最多{1}個字符")]
        public string Code { get; set; }

        /// <summary>
        /// 密碼
        /// </summary>
        [Display(Name = "密碼")]
        [Required(ErrorMessage = "{0}不能為空")]
        [StringLength(16, ErrorMessage = "{0}最多{1}個字符")]
        public string Pwd { get; set; }
    }

 

  Display標識提示欄位的名稱,Required表示必填,StringLength限制欄位的長度,當然還有其他一些內置特性,具體可參考官方檔案,列舉一些常見的驗證特性如下:

  • [CreditCard]:驗證屬性是否具有信用卡格式, 需要 JQuery 驗證其他方法,
  • [Compare]:驗證模型中的兩個屬性是否匹配,
  • [EmailAddress]:驗證屬性是否具有電子郵件格式,
  • [Phone]:驗證屬性是否具有電話號碼格式,
  • [Range]:驗證屬性值是否在指定的范圍內,
  • [RegularExpression]:驗證屬性值是否與指定的正則運算式匹配,
  • [Required]:驗證欄位是否不為 null, 有關此屬性的行為的詳細資訊,請參閱 [Required] 特性,
  • [StringLength]:驗證字串屬性值是否不超過指定長度限制,
  • [Url]:驗證屬性是否具有 URL 格式,
  • [Remote]:通過在服務器上呼叫操作方法來驗證客戶端上的輸入,

  上述說明了基本的模型驗證使用方法,以這種方式,同時結合T4模板,通過表物件生成模型驗證物體,省卻了在action中撰寫大量驗證代碼的作業,當然,一些必要的較為復雜的驗證,或結合資料庫操作的驗證,則單獨寫到action或其他應用模塊中,

  那么上述模型驗證在Web API中是怎么作業的呢?在Startup類的ConfigureServices添加如下代碼:

//模型引數驗證
    services.Configure<ApiBehaviorOptions>(options =>
    {
    options.InvalidModelStateResponseFactory = (context) =>
    {
        var error = context.ModelState.FirstOrDefault().Value;
        var message = error.Errors.FirstOrDefault(p => !string.IsNullOrWhiteSpace(p.ErrorMessage))?.ErrorMessage;                  

        return new JsonResult(new ApiResult { Message = message });                
    };
    });

 

  添加注冊示例Action代碼:

/// <summary>
        /// 注冊
        /// </summary>
        /// <param name="model"></param>
        /// <returns></returns>
        [HttpPost]
        public async Task<ApiResult> Register(RegisterEntity model)
        {
            ApiResult result = new ApiResult();

            var _code = CacheHelper.GetCache(model.Mobile);
            if (_code == null)
            {
                result.Message = "驗證碼過期或不存在";
                return result;
            }
            if (!model.Code.Equals(_code.ToString()))
            {
                result.Message = "驗證碼錯誤";
                return result;
            }

           /**
相關邏輯代碼
**/
            return result;
        }

 

  如此,通過配置ApiBehaviorOptions的方式,并讀取驗證錯誤資訊的第一條資訊并回傳,即完成了Web API中Action對請求引數的驗證作業,關于錯誤資訊Message的回傳,也可略作封裝,在此略,

 

七、日志使用

雖然.Net Core WebApi有自帶的日志管理功能,但不一定能較容易地滿足我們的需求,通常會采用第三方日志框架,典型的如:NLog、Log4Net,簡單介紹NLog日志組件的使用;

NLog的使用

① 通過NuGet安裝包:NLog.Web.AspNetCore,當前專案版本4.9.2;

② 專案根目錄新建一個NLog.config檔案,關鍵NLog.config的其他詳細配置,可參考官方檔案,這里作簡要配置如下;

<?xml version="1.0" encoding="utf-8"?>
<nlog xmlns="http://www.nlog-project.org/schemas/NLog.xsd" 
      xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" 
      autoReload="true" 
      throwExceptions="false" 
      internalLogLevel="Off" 
      internalLogFile="NlogRecords.log">
  <!--Nlog內部日志記錄為Off關閉-->
  <extensions>
    <add assembly="NLog.Web.AspNetCore" />
  </extensions>
  <targets> 
    <target name="log_file" xsi:type="File" fileName="${basedir}/logs/${shortdate}.log" 
            layout="${longdate} | ${level:uppercase=false} | ${message} ${onexception:${exception:format=tostring} ${newline} ${stacktrace} ${newline}" />
  </targets>  
    
  <rules>
    <!--跳過所有級別的Microsoft組件的日志記錄-->
    <logger name="Microsoft.*" final="true" />   
    <!--<logger name="logdb" writeTo="log_database" />-->
    <logger name="*" minlevel="Trace" writeTo="log_file" />  

  </rules>
</nlog>

<!--https://github.com/NLog/NLog/wiki/Getting-started-with-ASP.NET-Core-3-->

 

③ 調整Program.cs檔案如下;

public class Program
    {
        public static void Main(string[] args)
        {
            //CreateHostBuilder(args).Build().Run();           
       
            var logger = NLog.Web.NLogBuilder.ConfigureNLog("nlog.config").GetCurrentClassLogger();
            try
            {
                logger.Debug("init main");
                CreateHostBuilder(args).Build().Run();
            }
            catch (Exception exception)
            {
                //NLog: catch setup errors
                logger.Error(exception, "Stopped program because of exception");
                throw;
            }
            finally
            {
                // Ensure to flush and stop internal timers/threads before application-exit (Avoid segmentation fault on Linux)
                NLog.LogManager.Shutdown();
            }
        }

        public static IHostBuilder CreateHostBuilder(string[] args) =>
            Host.CreateDefaultBuilder(args)
                .ConfigureWebHostDefaults(webBuilder =>
                {
                    webBuilder.UseStartup<Startup>();
                }).ConfigureLogging(logging => {
                    logging.ClearProviders();
                    logging.SetMinimumLevel(Microsoft.Extensions.Logging.LogLevel.Trace);
                }).UseNLog();//依賴注入Nlog;
    }

其中Main函式里的捕獲例外代碼配置省略也是可以的,CreateHostBuilder下的UseNLog為必設項,

Controller通過注入呼叫如下:

 

public class WeatherForecastController : ControllerBase
    {
        private static readonly string[] Summaries = new[]
        {
            "Freezing", "Bracing", "Chilly", "Cool", "Mild", "Warm", "Balmy", "Hot", "Sweltering", "Scorching"
        };

        private readonly ILogger<WeatherForecastController> _logger;

        public WeatherForecastController(ILogger<WeatherForecastController> logger)
        {
            _logger = logger;
        }

        [HttpGet]
        public IEnumerable<WeatherForecast> Get()
        {
            _logger.LogInformation("測驗一條日志");

            var rng = new Random();
            return Enumerable.Range(1, 5).Select(index => new WeatherForecast
            {
                Date = DateTime.Now.AddDays(index),
                TemperatureC = rng.Next(-20, 55),
                Summary = Summaries[rng.Next(Summaries.Length)]
            })
            .ToArray();
        }

本地測驗后,即可在debug下看到logs目錄下生成的日志檔案, 

八、依賴注入

使用.Net Core少不了和依賴注入打交道,這也是.Net Core的設計思想之一,關于什么是依賴注入(DI),以及為什么要使用依賴注入,這里不再贅述,先來看一個簡單示例的依賴注入,

public interface IProductRepository
{
    IEnumerable<Product> GetAll();
}

public class ProductRepository : IProductRepository
{
public IEnumerable<Product> GetAll()
{ } }

Startup類進行注冊:

public void ConfigureServices(IServiceCollection services)
{
    services.AddScoped<IProductRepository, ProductRepository>();    
}

請求 IProductRepository 服務并用于呼叫 GetAll 方法:

public class ProductController : ControllerBase
{
    private readonly IProductRepository _productRepository;
   public ProductController(IProductRepository productRepository)
{ _productRepository = productRepository;
} public IEnumerable<Product> Get()
{ return _productRepository.GetAll();
} }

通過使用DI模式,來實作IProductRepository 介面,其實前述已多次出現通過建構式進行注入呼叫的示例,

生命周期  

services.AddScoped<IMyDependency, MyDependency>();
services.AddTransient<IMyDependency, MyDependency>();
services.AddSingleton<IMyDependency, MyDependency>();
  • Transient:每一次請求都會創建一個新實體;
  • Scoped:每個作用域生成周期內創建一個實體;
  • Singleton:單例模式,整個應用程式生命周期內只創建一個實體;

這里,需要根據具體的業務邏輯場景需求選擇注入相應的生命周期服務,

實際應用中,我們會有很多個服務需要注冊到ConfigureServices內,一個個寫入顯然繁瑣,而且容易忘記漏寫,一般地,我們可能會想到利用反射進行批量注入,并通過擴展的方式進行注入,譬如:

public static class AppServiceExtensions
    {
        /// <summary>
        /// 注冊應用程式域中的服務
        /// </summary>
        /// <param name="services"></param>
        public static void AddAppServices(this IServiceCollection services)
        {
            var ts = System.Reflection.Assembly.Load("CoreAPI.Data").GetTypes().Where(s => s.Name.EndsWith("Repository") || s.Name.EndsWith("Service")).ToArray();
            foreach (var item in ts.Where(s => !s.IsInterface))
            {
                var interfaceType = item.GetInterfaces();
                foreach (var typeArray in interfaceType)
                {
                    services.AddTransient(typeArray, item);
                }
            }
        }
    }
public void ConfigureServices(IServiceCollection services)
{
    services.AddAppServices();//批量注冊服務    
}

 

誠然,這樣配合系統自帶DI注入是能完成我們的批量注入需求的,但其實也有更多選擇,來幫我們簡化DI注冊,譬如選擇其他第三方組件:Scrutor、Autofac…

1、Scrutor的使用

Scrutor是基于微軟注入組件的一個擴展庫,簡單示例如下:

services.Scan(scan => scan
    .FromAssemblyOf<Startup>()
        .AddClasses(classes => classes.Where(s => s.Name.EndsWith("Repository") || s.Name.EndsWith("Service")))
.AsImplementedInterfaces() .WithTransientLifetime() );

以上代碼通過Scan方式批量注冊了以Repository、Service結尾的介面服務,其生命周期為Transient,該方式等同于前述的以反射方式的批量注冊服務,

關于Scrutor的其他用法,大家可以參見官方檔案,這里只做下引子,

2、Autofac

一般情況下,使用MS自帶的DI或采用Scrutor,即可滿足實際需要,如果有更高的應用需求,如要求屬性注入、甚至接管或取代MS自帶的DI,那么你可以選擇Autofac,關于Autofac的具體使用,在此不作詳敘,

 

九、快取

 MemoryCache使用

按官方說明,開發人員需合理說用快取,以及限制快取大小,Core運行時不會根據內容壓力限制快取大小,對于使用方式,依舊還是先行注冊,然后控制器呼叫:

public void ConfigureServices(IServiceCollection services)
{
    services.AddMemoryCache();//快取中間件
}
public class ProductController : ControllerBase
    {
        private IMemoryCache _cache;

        public ProductController(IMemoryCache memoryCache)
        {
            _cache = memoryCache;
        }

        [HttpGet]
        public DateTime GetTime()
        {
            string key = "_timeKey";          

            // Look for cache key.
            if (!_cache.TryGetValue(key, out DateTime cacheEntry))
            {
                // Key not in cache, so get data.
                cacheEntry = DateTime.Now;

                // Set cache options.
                var cacheEntryOptions = new MemoryCacheEntryOptions()
                    // Keep in cache for this time, reset time if accessed.
                    .SetSlidingExpiration(TimeSpan.FromSeconds(3));

                // Save data in cache.
                _cache.Set(key, cacheEntry, cacheEntryOptions);
            }

            return cacheEntry;
        }
    }

上述代碼快取了一個時間,并設定了滑動過期時間(指最后一次訪問后的過期時間)為3秒;如果需要設定絕對過期時間,將SetSlidingExpiration 改為SetAbsoluteExpiration即可,瀏覽重繪,每3秒后時間將更新,

附一個封裝好的Cache類如下:

public class CacheHelper
    {
        public static IMemoryCache _memoryCache = new MemoryCache(new MemoryCacheOptions());

        /// <summary>
        /// 快取絕對過期時間
        /// </summary>
        ///<param name="key">Cache鍵</param>
        ///<param name="value">快取的值</param>
        ///<param name="minute">minute分鐘后絕對過期</param>
        public static void SetChache(string key, object value, int minute)
        {
            if (value =https://www.cnblogs.com/ang/archive/2020/09/27/= null) return;
            _memoryCache.Set(key, value, new MemoryCacheEntryOptions()
                    .SetAbsoluteExpiration(TimeSpan.FromMinutes(minute)));
        }

        /// <summary>
        /// 快取相對過期,最后一次訪問后minute分鐘后過期
        /// </summary>
        ///<param name="key">Cache鍵</param>
        ///<param name="value">快取的值</param>
        ///<param name="minute">滑動過期分鐘</param>
        public static void SetChacheSliding(string key, object value, int minute)
        {
            if (value =https://www.cnblogs.com/ang/archive/2020/09/27/= null) return;
            _memoryCache.Set(key, value, new MemoryCacheEntryOptions()
                    .SetSlidingExpiration(TimeSpan.FromMinutes(minute)));
        }

        /// <summary>
        ///設定快取,如果不主動清空,會一直保存在記憶體中.
        /// </summary>
        ///<param name="key">Cache鍵值</param>
        ///<param name="value">給Cache[key]賦的值</param>
        public static void SetChache(string key, object value)
        {
            _memoryCache.Set(key, value);
        }

        /// <summary>
        ///清除快取
        /// </summary>
        ///<param name="key">cache鍵</param>
        public static void RemoveCache(string key)
        {
            _memoryCache.Remove(key);
        }

        /// <summary>
        ///根據key值,回傳Cache[key]的值
        /// </summary>
        ///<param name="key"></param>
        public static object GetCache(string key)
        {
            //return _memoryCache.Get(key);
            if (key != null && _memoryCache.TryGetValue(key, out object val))
            {
                return val;
            }
            else
            {
                return default;
            }
        }

        /// <summary>
        /// 通過Key值回傳泛型物件
        /// </summary>
        /// <typeparam name="T"></typeparam>
        /// <param name="key"></param>
        /// <returns></returns>
        public static T GetCache<T>(string key)
        {
            if (key != null && _memoryCache.TryGetValue<T>(key, out T val))
            {
                return val;
            }
            else
            {
                return default;
            }
        }

    }
View Code

 

十、例外處理

定義例外處理中間件

這里主要針對全域例外進行捕獲處理并記錄日志,并以統一的json格式回傳給介面呼叫者;說例外處理前先提下中間件,關于什么是中間件,在此不在贅述,一個中間件其基本的結構如下:

public class CustomMiddleware
{
    private readonly RequestDelegate _next;

    public CustomMiddleware(RequestDelegate next)
    {
        _next = next;
    }
    
    public async Task Invoke(HttpContext httpContext)
    {       
        await _next(httpContext);
    }
}

下面我們定義自己的全域例外處理中間件,代碼如下:

    public class CustomExceptionMiddleware
    {
        private readonly RequestDelegate _next;
        private readonly ILogger<CustomExceptionMiddleware> _logger;      

        public CustomExceptionMiddleware(RequestDelegate next, ILogger<CustomExceptionMiddleware> logger)
        {
            _next = next;
            _logger = logger;
        }

        public async Task Invoke(HttpContext httpContext)
        {
            try
            {
                await _next(httpContext);
            }
            catch (Exception ex)
            {
                _logger.LogError(ex,"Unhandled exception...");
                await HandleExceptionAsync(httpContext, ex);
            }         
        }

        private Task HandleExceptionAsync(HttpContext httpContext, Exception ex)
        {
            var result = JsonConvert.SerializeObject(new { isSuccess = false, message = ex.Message });
            httpContext.Response.ContentType = "application/json;charset=utf-8";
            return httpContext.Response.WriteAsync(result);
        }
    }

    /// <summary>
    /// 以擴展方式添加中間件
    /// </summary>
    public static class CustomExceptionMiddlewareExtensions
    {
        public static IApplicationBuilder UseCustomExceptionMiddleware(this IApplicationBuilder builder)
        {
            return builder.UseMiddleware<CustomExceptionMiddleware>();
        }
    }

然后在Startup類的Configure方法里添加上述擴展的中間件,見加粗部分:

public void Configure(IApplicationBuilder app, IWebHostEnvironment env)
        {
            if (env.IsDevelopment())
            {
                app.UseDeveloperExceptionPage();
            }

            //全域例外處理
            app.UseCustomExceptionMiddleware();
        }

 在HandleExceptionAsync方法中,為方便開發和測驗,這里將系統的錯誤回傳給了介面呼叫者,實際生產環境中可統一回傳固定的錯誤Message訊息,

例外狀態碼的處理

關于http狀態碼,常見的如正常回傳的200,其他401、403、404、502等等等等,因為系統有時候并不總是回傳200成功,對于回傳非200的例外狀態碼,WebApi也要做到相應的處理,以便介面呼叫者能正確接收,譬如緊接下來的JWT認證,當認證令牌過期或沒有權限時,系統實際會回傳401、403,但介面并不提供有效的可接收的回傳,因此,這里列舉一些常見的例外狀態碼,并以200方式提供給介面呼叫者,在Startup類的Configure方法里添加代碼如下:

app.UseStatusCodePages(async context =>
            {
                //context.HttpContext.Response.ContentType = "text/plain";  
                context.HttpContext.Response.ContentType = "application/json;charset=utf-8";

                int code = context.HttpContext.Response.StatusCode;
                string message =
                 code switch
                 {
                     401 => "未登錄",
                     403 => "訪問拒絕",
                     404 => "未找到",
                     _ => "未知錯誤",
                 };

                context.HttpContext.Response.StatusCode = StatusCodes.Status200OK;
                await context.HttpContext.Response.WriteAsync(Newtonsoft.Json.JsonConvert.SerializeObject(new
                {
                    isSuccess = false,
                    code,
                    message
                }));

            });

代碼很簡單,這里使用系統自帶的例外處理中間件UseStatusCodePages,當然,你還可以自定義過濾器處理例外,不過不推薦,簡單高效直接才是需要的,

關于.NET Core的例外處理中間件,還有其他諸如 UseExceptionHandler、UseStatusCodePagesWithRedirects等等,不同的中間件有其適用的環境,有的可能更適用于MVC或其他應用場景上,找到合適的即可,

題外話:大家也可以將UseStatusCodePages處理例外狀態碼的操作封裝到前述的全域例外處理中間件中,

 

十一、應用安全與JWT認證

關于什么是JWT,在此不作贅述,實際應用中,為了部分介面的安全性,譬如需要身份認證才能訪問的介面資源,對于Web API而言,一般會采用token令牌進行認證,服務端結合快取來實作,

那為什么要選擇JWT認證呢?原因無外乎以下:服務端不進行保存、無狀態、適合移動端、適合分布式、標準化等等,關于JWT的使用如下:

通過NuGget安裝包:Microsoft.AspNetCore.Authentication.JwtBearer,當前示例版本3.1.5;

ConfigureServices進行注入,默認以Bearer命名,這里你也可以改成其他名字,保持前后一致即可,注意加粗部分,代碼如下: 

appsettings.json添加JWT配置節點(見前述【組態檔】),添加JWT相關認證類:

public static class JwtSetting
    {
        public static JwtConfig Setting { get; set; } = new JwtConfig();
    }

    public class JwtConfig
    {
        public string Secret { get; set; }
        public string Issuer { get; set; }
        public string Audience { get; set; }
        public int AccessExpiration { get; set; }
        public int RefreshExpiration { get; set; }
    }

采用前述系結靜態類的方式讀取JWT配置,并進行注入:

public Startup(IConfiguration configuration, IWebHostEnvironment env)
        {
            //Configuration = configuration;

            var builder = new ConfigurationBuilder()
              .SetBasePath(env.ContentRootPath)
              .AddJsonFile("appsettings.json", optional: true, reloadOnChange: true);

            Configuration = builder.Build();            

            configuration.GetSection("SystemConfig").Bind(MySettings.Setting);//系結靜態配置類
            configuration.GetSection("JwtTokenConfig").Bind(JwtSetting.Setting);//同上

        }

        public IConfiguration Configuration { get; }

        // This method gets called by the runtime. Use this method to add services to the container.
        public void ConfigureServices(IServiceCollection services)
        {
         
            #region JWT認證注入            

            JwtSecurityTokenHandler.DefaultInboundClaimTypeMap.Clear();
            services.AddAuthentication("Bearer")
                .AddJwtBearer("Bearer", options =>
                {
                    options.RequireHttpsMetadata = false;

                    options.TokenValidationParameters = new TokenValidationParameters
                    {
                        ValidateIssuer = true,
                        ValidateAudience = true,
                        ValidateLifetime = true,
                        ValidateIssuerSigningKey = true,
                        ValidIssuer = JwtSetting.Setting.Issuer,
                        ValidAudience = JwtSetting.Setting.Audience,
                        IssuerSigningKey = new SymmetricSecurityKey(System.Text.Encoding.UTF8.GetBytes(JwtSetting.Setting.Secret))
                    };
                });

            #endregion
            
        }

給Swagger添加JWT認證支持,完成后,Swagger頁面會出現鎖的標識,獲取token后填入Value(Bearer token形式)項進行Authorize登錄即可,Swagger配置JWT見加粗部分:

services.AddSwaggerGen(c =>
            {
                c.SwaggerDoc("v1", new OpenApiInfo
                {
                    Title = "My API",
                    Version = "v1",
                    Description = "API檔案描述",
                    Contact = new OpenApiContact
                    {
                        Email = "[email protected]",
                        Name = "測驗專案",
                        //Url = new Uri("http://t.abc.com/")
                    },
                    License = new OpenApiLicense
                    {
                        Name = "BROOKE許可證",
                        //Url = new Uri("http://t.abc.com/")
                    }
                });


                // 為 Swagger JSON and UI設定xml檔案注釋路徑
                //var basePath = Path.GetDirectoryName(typeof(Program).Assembly.Location);//獲取應用程式所在目錄(不受作業目錄影響)
                //var xmlPath = Path.Combine(basePath, "CoreAPI_Demo.xml");
                //c.IncludeXmlComments(xmlPath, true);

                var xmlFile = $"{Assembly.GetExecutingAssembly().GetName().Name}.xml";
                var xmlPath = Path.Combine(AppContext.BaseDirectory, xmlFile);
                c.IncludeXmlComments(xmlPath);

                #region JWT認證Swagger授權

                c.AddSecurityDefinition("Bearer", new OpenApiSecurityScheme
                {
                    Description = "JWT授權(資料將在請求頭header中進行傳輸) 直接在下框中輸入Bearer {token}(中間是空格)",
                    Name = "Authorization",
                    In = ParameterLocation.Header,
                    Type = SecuritySchemeType.ApiKey,
                    BearerFormat = "JWT",
                    Scheme = "Bearer"
                });

                c.AddSecurityRequirement(new OpenApiSecurityRequirement()
                {
                    {
                        new OpenApiSecurityScheme
                        {
                            Reference = new OpenApiReference {
                                Type = ReferenceType.SecurityScheme,
                                Id = "Bearer"
                            }
                        },
                        new string[] { }
                    }
                });

                #endregion

            });

Starup類添加Configure注冊,注意,需放到 app.UseAuthorization();前面:

public void Configure(IApplicationBuilder app, IWebHostEnvironment env)
        {
            app.UseAuthentication();//jwt認證

            app.UseAuthorization();
        
        }

這樣,JWT就基本配置完畢,接下來實施認證登錄和授權,模擬操作如下:

[HttpPost]
        public async Task<ApiResult> Login(LoginEntity model)      
        {           
            ApiResult result = new ApiResult();          

            //驗證用戶名和密碼
            var userInfo = await _memberService.CheckUserAndPwd(model.User, model.Pwd);
            if (userInfo == null)
            {
                result.Message = "用戶名或密碼不正確";
                return result;
            }
            var claims = new Claim[]
            {
                new Claim(ClaimTypes.Name,model.User),
                new Claim(ClaimTypes.Role,"User"),
                new Claim(JwtRegisteredClaimNames.Sub,userInfo.MemberID.ToString()),

            };

            var key = new SymmetricSecurityKey(System.Text.Encoding.UTF8.GetBytes(JwtSetting.Setting.Secret));
            var expires = DateTime.Now.AddDays(1);
            var token = new JwtSecurityToken(
                        issuer: JwtSetting.Setting.Issuer,
                        audience: JwtSetting.Setting.Audience,
                        claims: claims,
                        notBefore: DateTime.Now,
                        expires: expires,
                        signingCredentials: new SigningCredentials(key, SecurityAlgorithms.HmacSha256));

            //生成Token
            string jwtToken = new JwtSecurityTokenHandler().WriteToken(token);

            //更新最后登錄時間
            await _memberService.UpdateLastLoginTime(userInfo.MemberID);

            result.IsSuccess= 1;            
            result.ResultData["token"] = jwtToken;
            result.Message = "授權成功!";
            return result;

        }

上述代碼模擬登錄操作(賬號密碼登錄,成功后設定有效期一天),生成token并回傳,前端呼叫者拿到token后以諸如localstorage方式進行存盤,調取授權介面時,添加該token到header(Bearer token)進行介面請求,接下來,給需要身份授權的Controller或Action打上Authorize標識:

[Authorize]
[Route("api/[controller]/[action]")]
public class UserController : ControllerBase
{
}

如果要添加基于角色的授權,可限制操作如下:

[Authorize(Roles = "user")]
[Route("api/[controller]/[action]")]
public class UserController : ControllerBase
{
}

//多個角色也可以逗號分隔
[Authorize(Roles = "Administrator,Finance")]
[Route("api/[controller]/[action]")]
public class UserController : ControllerBase
{
}

不同的角色資訊,可通過登錄設定ClaimTypes.Role進行配置;當然,這里只是簡單的示例說明角色服務的應用,復雜的可通過注冊策略服務,并結合資料庫進行動態配置,

這樣,一個簡單的基于JWT認證授權的作業就完成了,

 

十二、跨域

 前后端分離,會涉及到跨域問題,簡單的支持跨域操作如下:

添加擴展支持

public static class CrosExtensions
    {
        public static void ConfigureCors(this IServiceCollection services)
        {          

            services.AddCors(options => options.AddPolicy("CorsPolicy",
                builder =>
                {
                    builder.AllowAnyMethod()
                        .SetIsOriginAllowed(_ => true)
                        .AllowAnyHeader()
                        .AllowCredentials();
                }));

            //services.AddCors(options => options.AddPolicy("CorsPolicy",
            //builder =>
            //{
            //    builder.WithOrigins(new string[] { "http://localhost:13210" })
            //        .AllowAnyMethod()
            //        .AllowAnyHeader()
            //        .AllowCredentials();
            //}));


        }
    }

Startup類添加相關注冊如下:

public void ConfigureServices(IServiceCollection services)
{
    services.ConfigureCors();
}

 

public void Configure(IApplicationBuilder app, IWebHostEnvironment env)
        {
            app.UseCors("CorsPolicy");//跨域          
        }

這樣,一個簡單跨域操作就完成了,你也可以通過設定WithOrigins、WithMethods等方法限制請求地址來源和請求方式,

 

至此,全篇結束,本篇涉及到的原始碼地址:https://github.com/Brooke181/CoreAPI_Demo

下一篇介紹Dapper在.NET Core中的使用,謝謝支持!

 

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

標籤:.NET技术

上一篇:.NET Core 使用 Consul 服務注冊發現

下一篇:.NET中的本地快取(資料分拆+lock鎖)

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

熱門瀏覽
  • WebAPI簡介

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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