【asp.net core】實現動態 Web API

来源:https://www.cnblogs.com/h82258652/archive/2020/03/10/12454554.html
-Advertisement-
Play Games

序言:遠程工作已經一個月了,最近也算是比較閑,每天早上起床打個卡,快速弄完當天要做的工作之後就快樂摸魚去了。之前在用 ABP 框架(舊版)的時候就覺得應用服務層寫起來真的爽,為什麼實現了個 IApplicationService 的空介面就可以變成 Web API,可惜的是之前一直沒空去研究這一塊的 ...


序言:

遠程工作已經一個月了,最近也算是比較閑,每天早上起床打個卡,快速弄完當天要做的工作之後就快樂摸魚去了。之前在用 ABP 框架(舊版)的時候就覺得應用服務層寫起來真的爽,為什麼實現了個 IApplicationService 的空介面就可以變成 Web API,可惜的是之前一直沒空去研究這一塊的原理及其實現,園子里也找不到相關實現原理的文章(舊版 ABP 的倒是有,但是 asp.net core 無法參考)。最近閑起來,就看了一下 abp vnext 的源碼,並且也參考了一下曉晨Master 介紹的 Panda.DynamicWebApi。我自己也簡單實現了一遍動態 Web API,不禁感嘆 asp.net core 設計之精妙。

abp vnexthttps://abp.io

Panda.DynamicWebApihttps://github.com/pdafx/Panda.DynamicWebApi

這裡先感謝這兩個庫的相關人員,沒有他們的工作,本文也出現不了。另外在此聲明,本文意在探究其實現原理並實現一個簡易版本,若無把握請勿用於生產環境。


正文:

首先先創建我們的解決方案如下:

Snipaste_2020-03-09_20-34-28

因為動態 Web API 這一功能是與業務無關的,而且為了復用,我們應該把這一功能的實現寫到一個單獨的類庫當中。上圖中 Demo 項目是 asp.net core 3.1 版本的 Web API 項目,用於演示我們的簡易動態 Web API,而 SimpleDynamicWebAPI 的 .net standard 2.0 項目則是我們的簡易動態 Web API 項目。


要實現動態 Web API,首先要做的第一件事情就是要有一個規則,來判定一個類是不是動態 Web API。在 abp vnext 當中,主要提供兩種方式一個是實現 IRemoteService 介面(實際開發過程中一般都是實現 IApplicationService 介面),另一種方式標記 RemoteServiceAttribute。而在 Panda.DynamicWebApi 中,則是實現 IDynamicWebApi 介面並且標記 DynamicWebApi。因為本文是要實現簡易版本,因此只選空介面方式。在 SimpleDynamicWebAPI 項目中創建如下空介面:

namespace SimpleDynamicWebAPI
{
    public interface IApplicationService
    {
    }
}

接下來,我們有了 IApplicationService 介面,我們也知道實現了這個介面的類是要成為動態 Web API 的,但這個是我們所知道的規則,asp.net core 框架它是不知道的,我們需要把這個規則告訴它。

這一塊 abp vnext 有點複雜,我們參考 Panda.DynamicWebAPI 的實現:

Snipaste_2020-03-09_20-55-02

https://github.com/pdafx/Panda.DynamicWebApi/blob/master/src/Panda.DynamicWebApi/DynamicWebApiServiceExtensions.cs#L46

Snipaste_2020-03-09_20-55-48

https://github.com/pdafx/Panda.DynamicWebApi/blob/master/src/Panda.DynamicWebApi/DynamicWebApiControllerFeatureProvider.cs

上面圖中 DynamicWebApiControllerFeatureProvider 的 IsController 方法很明顯了。查看 msdn

Snipaste_2020-03-09_21-03-07

粗俗點翻譯過來就是判斷一個類是不是控制器。


接下來開始依樣畫葫蘆。首先一點 ControllerFeatureProvider 類是屬於 asp.net core 的,理論上是位於 Microsoft.AspNetCore.Mvc.Core 這個 nuget 包的,但是這個包的 3.x 版本並沒有發佈在 nuget 上。如果我們的 SimpleDynamicWebAPI 引用 2.x 版本的,而 Demo 項目又是 3.x 版本的,則很可能會引起衝突。保險起見,我們修改 SimpleDynamicWebAPI 為一個 asp.net core 的類庫。反正這個庫本來也不可能會被其它類型諸如 WPF 的項目引用。

修改 SimpleDynamicWebAPI.csproj 如下:

<Project Sdk="Microsoft.NET.Sdk.Web">
  <PropertyGroup>
    <TargetFramework>netcoreapp3.1</TargetFramework>
    <OutputType>Library</OutputType>
  </PropertyGroup>
</Project>

接下來創建 ApplicationServiceControllerFeatureProvider 類,並修改代碼如下:

using Microsoft.AspNetCore.Mvc.Controllers;
using System.Reflection;

namespace SimpleDynamicWebAPI
{
    public class ApplicationServiceControllerFeatureProvider : ControllerFeatureProvider
    {
        protected override bool IsController(TypeInfo typeInfo)
        {
            if (typeof(IApplicationService).IsAssignableFrom(typeInfo))
            {
                if (!typeInfo.IsInterface &&
                    !typeInfo.IsAbstract &&
                    !typeInfo.IsGenericType &&
                    typeInfo.IsPublic)
                {
                    return true;
                }
            }

            return false;
        }
    }
}

首先先要判斷是不是實現了 IApplicationService 介面,這個是我們一開始所定下的規則。

接下來,1、如果一個介面即使它實現了 IApplicationService,但它仍然不能是一個控制器,那是因為介面是無法實例化的;2、抽象類同理,也是因為無法實例化;3、泛型類也不允許,因為需要確切的類型才能實例化;4、public 代表著公開,可被外界訪問,如果一個類不是 public 的,那麼就不應該成為一個動態 Web API 控制器。

接下來就是要把這個 ApplicationServiceControllerFeatureProvider 加入到 asp.net core 框架中。

創建 SimpleDynamicWebApiExtensions 擴展類,修改代碼如下:

using Microsoft.Extensions.DependencyInjection;
using System;

namespace SimpleDynamicWebAPI
{
    public static class SimpleDynamicWebApiExtensions
    {
        public static IMvcBuilder AddDynamicWebApi(this IMvcBuilder builder)
        {
            if (builder == null)
            {
                throw new ArgumentNullException(nameof(builder));
            }

            builder.ConfigureApplicationPartManager(applicationPartManager =>
            {
                applicationPartManager.FeatureProviders.Add(new ApplicationServiceControllerFeatureProvider());
            });
            return builder;
        }

        public static IMvcCoreBuilder AddDynamicWebApi(this IMvcCoreBuilder builder)
        {
            if (builder == null)
            {
                throw new ArgumentNullException(nameof(builder));
            }

            builder.ConfigureApplicationPartManager(applicationPartManager =>
            {
                applicationPartManager.FeatureProviders.Add(new ApplicationServiceControllerFeatureProvider());
            });
            return builder;
        }
    }
}

因為 ConfigureApplicationPartManager 擴展方法分別在 IMvcBuilder 和 IMvcCoreBuilder 上都有,所以我們也只能寫兩遍。當然參照 abp vnext 或 Panda.DynamicWebApi 從 services 中獲取 ApplicationPartManager 對象實例也是可行的。

接下來回到 Demo 項目,在 AddControllers 後面加上 AddDynamicWebApi:

public void ConfigureServices(IServiceCollection services)
{
    services.AddControllers().AddDynamicWebApi();
}


現在我們已經完成第一步了,實現了 IApplicationService 介面的類將被視作控制器處理。但僅僅這樣並不足夠,假設有多個類同時實現 IApplicationService 介面,那應該如何映射呢,如果沒錯的話,這個時候你應該會想到是——路由。我們還需要做的工作就是把這些控制器與路由配置起來。

abp vnext 這塊為了在配置過程中獲取 services 而延遲載入導致包了一層,有點複雜。這裡參考 Panda.DynamicWebApi

Snipaste_2020-03-09_22-11-13

https://github.com/pdafx/Panda.DynamicWebApi/blob/master/src/Panda.DynamicWebApi/DynamicWebApiServiceExtensions.cs#L51

註釋告訴了我們這裡是配置控制器的路由,感謝作者大大。

繼續畫葫蘆,創建 ApplicationServiceConvention 類並實現 IApplicationModelConvention 介面:

using Microsoft.AspNetCore.Mvc.ApplicationModels;
using System;

namespace SimpleDynamicWebAPI
{
    public class ApplicationServiceConvention : IApplicationModelConvention
    {
        public void Apply(ApplicationModel application)
        {
            throw new NotImplementedException();
        }
    }
}

Apply 方法的實現等下再考慮,先把它註冊到 asp.net core 框架,修改 SimpleDynamicWebApiExtensions 擴展類如下:

using Microsoft.AspNetCore.Mvc;
using Microsoft.Extensions.DependencyInjection;
using System;

namespace SimpleDynamicWebAPI
{
    public static class SimpleDynamicWebApiExtensions
    {
        public static IMvcBuilder AddDynamicWebApi(this IMvcBuilder builder)
        {
            if (builder == null)
            {
                throw new ArgumentNullException(nameof(builder));
            }

            builder.ConfigureApplicationPartManager(applicationPartManager =>
            {
                applicationPartManager.FeatureProviders.Add(new ApplicationServiceControllerFeatureProvider());
            });

            builder.Services.Configure<MvcOptions>(options =>
            {
                options.Conventions.Add(new ApplicationServiceConvention());
            });

            return builder;
        }

        public static IMvcCoreBuilder AddDynamicWebApi(this IMvcCoreBuilder builder)
        {
            if (builder == null)
            {
                throw new ArgumentNullException(nameof(builder));
            }

            builder.ConfigureApplicationPartManager(applicationPartManager =>
            {
                applicationPartManager.FeatureProviders.Add(new ApplicationServiceControllerFeatureProvider());
            });

            builder.Services.Configure<MvcOptions>(options =>
            {
                options.Conventions.Add(new ApplicationServiceConvention());
            });

            return builder;
        }
    }
}

對服務容器中的 MvcOptions 進行配置,添加上 ApplicationServiceConvention。ok,接下來回到考慮 Apply 方法實現的問題了。

這裡參考 abp vnext:

Snipaste_2020-03-09_22-26-48

https://github.com/abpframework/abp/blob/master/framework/src/Volo.Abp.AspNetCore.Mvc/Volo/Abp/AspNetCore/Mvc/Conventions/AbpServiceConvention.cs#L33

上圖中的 ApplyForControllers 方法的方法體關鍵部分很好懂,foreach 遍歷了所有的控制器,如果控制器實現了 IRemoteService 介面或者標記了 RemoteServiceAttribute,則調用 ConfigureRemoteService 進一步處理。因為我們的簡易版本是只有介面,else 部分的我們就不需要了。

修改 ApplicationServiceConvention 代碼如下:

using Microsoft.AspNetCore.Mvc.ApplicationModels;
using System;

namespace SimpleDynamicWebAPI
{
    public class ApplicationServiceConvention : IApplicationModelConvention
    {
        public void Apply(ApplicationModel application)
        {
            foreach (var controller in application.Controllers)
            {
                if (typeof(IApplicationService).IsAssignableFrom(controller.ControllerType))
                {
                    ConfigureApplicationService(controller);
                }
            }
        }

        private void ConfigureApplicationService(ControllerModel controller)
        {
            throw new NotImplementedException();
        }
    }
}

那 abp vnext 的 ConfigureRemoteService 方法中又幹了什麼呢?跳轉到 ConfigureRemoteService 的實現:

Snipaste_2020-03-09_22-36-53

做了三件事情。

1、ConfigureApiExplorer。ApiExplorer,簡單點說就是 API 是否可被髮現。舉個慄子,加入你寫了一個 Web API,項目又配置了 swagger,而且你又想 swagger 不顯示這個 Web API 的話,那麼可以在 Action 上加上:

[ApiExplorerSettings(IgnoreApi = true)]

具體這裡就不說了,大家可以自行 google。

2、ConfigureSelector。Selector,選擇器,可能不太好理解。但是第三個明顯是配置參數,那麼第二這個只能是配置路由了,這個方法將會是我們的關鍵。

3、ConfigureParameters。第二點說了,配置參數。


那麼繼續修改我們的 ApplicationServiceConvention 類並且實現我們的 ConfigureApiExplorer:

using Microsoft.AspNetCore.Mvc.ApplicationModels;
using System;

namespace SimpleDynamicWebAPI
{
    public class ApplicationServiceConvention : IApplicationModelConvention
    {
        public void Apply(ApplicationModel application)
        {
            foreach (var controller in application.Controllers)
            {
                if (typeof(IApplicationService).IsAssignableFrom(controller.ControllerType))
                {
                    ConfigureApplicationService(controller);
                }
            }
        }

        private void ConfigureApplicationService(ControllerModel controller)
        {
            ConfigureApiExplorer(controller);
            ConfigureSelector(controller);
            ConfigureParameters(controller);
        }

        private void ConfigureApiExplorer(ControllerModel controller)
        {
            if (!controller.ApiExplorer.IsVisible.HasValue)
            {
                controller.ApiExplorer.IsVisible = true;
            }

            foreach (var action in controller.Actions)
            {
                if (!action.ApiExplorer.IsVisible.HasValue)
                {
                    action.ApiExplorer.IsVisible = true;
                }
            }
        }

        private void ConfigureSelector(ControllerModel controller)
        {
            throw new NotImplementedException();
        }

        private void ConfigureParameters(ControllerModel controller)
        {
            throw new NotImplementedException();
        }
    }
}

ConfigureApiExplorer 這塊,IsVisible 只要沒有值,就無腦設為 true 好了。

接下來 ConfigureSelector 看 abp vnext 的實現:

Snipaste_2020-03-09_23-01-01

https://github.com/abpframework/abp/blob/master/framework/src/Volo.Abp.AspNetCore.Mvc/Volo/Abp/AspNetCore/Mvc/Conventions/AbpServiceConvention.cs#L170

首先第一行 RemoveEmptySelectors 這是一個關鍵點。雖然我們的動態 Web API 控制器一開始並沒有配置路由,但實際上 asp.net core 框架會為此生成一些空白信息。abp vnext 在這裡就抹除掉了這些空白信息。而 Panda.DynamicWebApi 雖然沒有這樣乾,但是後面的判斷邏輯就相對複雜了一些(大大別打我)。

我們的 RemoveEmptySelectors:

private void RemoveEmptySelectors(IList<SelectorModel> selectors)
{
    for (var i = selectors.Count - 1; i >= 0; i--)
    {
        var selector = selectors[i];
        if (selector.AttributeRouteModel == null &&
            (selector.ActionConstraints == null || selector.ActionConstraints.Count <= 0) &&
            (selector.EndpointMetadata == null || selector.EndpointMetadata.Count <= 0))
        {
            selectors.Remove(selector);
        }
    }
}

使用倒序刪除小技巧,就不需要擔心下標越界的問題了。

if 第一行明顯可以看出判斷路由信息是否存在,第二行判斷的 Action 的約束,而約束則是指 HttpGet、HttpPost 這種約束,第三行判斷了端點元數據信息,例如標記了什麼 Attribute 之類的。假如這些都沒有,那麼這條 selector 就可以斷定為空白信息了。

接下來回到 abp vnext 代碼截圖的 181 行:

Snipaste_2020-03-09_23-25-51

https://github.com/abpframework/abp/blob/master/framework/src/Volo.Abp.AspNetCore.Mvc/Volo/Abp/AspNetCore/Mvc/Conventions/AbpServiceConvention.cs#L181

假如移除過空白信息後仍然有路由的話,則後續不進行處理。

接下來的 foreach 就開始處理 Action 了。先完善我們的代碼,再開始處理 Action 的路由:

using Microsoft.AspNetCore.Mvc.ApplicationModels;
using System;
using System.Collections.Generic;
using System.Linq;

namespace SimpleDynamicWebAPI
{
    public class ApplicationServiceConvention : IApplicationModelConvention
    {
        public void Apply(ApplicationModel application)
        {
            foreach (var controller in application.Controllers)
            {
                if (typeof(IApplicationService).IsAssignableFrom(controller.ControllerType))
                {
                    ConfigureApplicationService(controller);
                }
            }
        }

        private void ConfigureApplicationService(ControllerModel controller)
        {
            ConfigureApiExplorer(controller);
            ConfigureSelector(controller);
            ConfigureParameters(controller);
        }

        private void ConfigureApiExplorer(ControllerModel controller)
        {
            if (!controller.ApiExplorer.IsVisible.HasValue)
            {
                controller.ApiExplorer.IsVisible = true;
            }

            foreach (var action in controller.Actions)
            {
                if (!action.ApiExplorer.IsVisible.HasValue)
                {
                    action.ApiExplorer.IsVisible = true;
                }
            }
        }

        private void ConfigureSelector(ControllerModel controller)
        {
            RemoveEmptySelectors(controller.Selectors);

            if (controller.Selectors.Any(temp => temp.AttributeRouteModel != null))
            {
                return;
            }

            foreach (var action in controller.Actions)
            {
                ConfigureSelector(action);
            }
        }

        private void ConfigureSelector(ActionModel action)
        {
            throw new NotImplementedException();
        }

        private void ConfigureParameters(ControllerModel controller)
        {
            throw new NotImplementedException();
        }

        private void RemoveEmptySelectors(IList<SelectorModel> selectors)
        {
            for (var i = selectors.Count - 1; i >= 0; i--)
            {
                var selector = selectors[i];
                if (selector.AttributeRouteModel == null &&
                    (selector.ActionConstraints == null || selector.ActionConstraints.Count <= 0) &&
                    (selector.EndpointMetadata == null || selector.EndpointMetadata.Count <= 0))
                {
                    selectors.Remove(selector);
                }
            }
        }
    }
}

開始處理 Action 的路由,參考 abp vnext 的 194 行到 212 行:

Snipaste_2020-03-09_23-32-13

第一行仍然是移除空白信息。

關鍵在最後的判斷,假如沒有 selector 的話,加上就是了。但是如果已經有了呢?那就修改唄。舉個慄子,假如我們實現 IApplicationService 介面的類的一個方法標記了 HttpGet,那麼這個 Action 是有約束的,但是它卻是沒有路由的。這幾行無論是 abp vnext 還是 Panda.DynamicWebApi 都是一樣的。

初步實現添加 selector 方法,這裡我叫它 AddApplicationServiceSelector:

private void AddApplicationServiceSelector(ActionModel action)
{
    var selector = new SelectorModel();
    selector.AttributeRouteModel = new AttributeRouteModel(new RouteAttribute(CalculateRouteTemplate(action)));
    selector.ActionConstraints.Add(new HttpMethodActionConstraint(new[] { GetHttpMethod(action) }));

    action.Selectors.Add(selector);
}

private string CalculateRouteTemplate(ActionModel action)
{
    throw new NotImplementedException();
}

private string GetHttpMethod(ActionModel action)
{
    throw new NotImplementedException();
}

接下來我們需要添加路由並且配置約束。

要計算路由,我們先舉個慄子(嗯,第三顆慄子了)。假設我們有一個叫 BookController 的 API 控制器,有一個叫 Save 的 Action,那麼它的路由一般就是:

api/books/{id}/save

也就是說,一般 API 控制器的路由如下:

api/[controller]s(/{id})?(/[action])?

那麼我們大概能寫出如下代碼:

private string CalculateRouteTemplate(ActionModel action)
{
    var routeTemplate = new StringBuilder();
    routeTemplate.Append("api");

    // 控制器名稱部分
    var controllerName = action.Controller.ControllerName;
    if (controllerName.EndsWith("ApplicationService"))
    {
        controllerName = controllerName.Substring(0, controllerName.Length - "ApplicationService".Length);
    }
    else if (controllerName.EndsWith("AppService"))
    {
        controllerName = controllerName.Substring(0, controllerName.Length - "AppService".Length);
    }
    controllerName += "s";
    routeTemplate.Append($"/{controllerName}");

    // id 部分
    if (action.Parameters.Any(temp => temp.ParameterName == "id"))
    {
        routeTemplate.Append("/{id}");
    }

    // Action 名稱部分
    var actionName = action.ActionName;
    if (actionName.EndsWith("Async"))
    {
        actionName = actionName.Substring(0, actionName.Length - "Async".Length);
    }
    var trimPrefixes = new[]
    {
        "GetAll","GetList","Get",
        "Post","Create","Add","Insert",
        "Put","Update",
        "Delete","Remove",
        "Patch"
    };
    foreach (var trimPrefix in trimPrefixes)
    {
        if (actionName.StartsWith(trimPrefix))
        {
            actionName = actionName.Substring(trimPrefix.Length);
            break;
        }
    }
    if (!string.IsNullOrEmpty(actionName))
    {
        routeTemplate.Append($"/{actionName}");
    }

    return routeTemplate.ToString();
}

以 api 開頭。

控制器部分,如果名字結尾是 ApplicationService 或者 AppService,那就裁掉。並且變為複數。因為這裡是簡易版,直接加 s 了是。實際建議使用 Inflector 等之類的庫。不然 bus 這種詞直接加 s 就太奇怪了。

id 部分沒啥好說的。

最後是 Action 部分,假如是 Async 結尾的,裁掉。接下來看開頭是不是以 Get、Post、Create 等等這些開頭,是的話也裁掉,註意要先判斷 GetAll 和 GetList 然後再判斷 Get。因為最後裁掉之後有可能是空字元串,所以還需要判斷一下再確定是否添加到路由中。

通過 Action 部分的計算,之前我們剩下的 GetHttpMethod 方法也很好寫了:

private string GetHttpMethod(ActionModel action)
{
    var actionName = action.ActionName;
    if (actionName.StartsWith("Get"))
    {
        return "GET";
    }

    if (actionName.StartsWith("Put") || actionName.StartsWith("Update"))
    {
        return "PUT";
    }

    if (actionName.StartsWith("Delete") || actionName.StartsWith("Remove"))
    {
        return "DELETE";
    }

    if (actionName.StartsWith("Patch"))
    {
        return "PATCH";
    }

    return "POST";
}

根據 Action 名開頭返回 Http 方法就是了,如果什麼都匹配不上就假定 POST。


添加 Selector 總算寫完了,修改 Selector 還難麽?實現我們自己的 NormalizeSelectorRoutes 方法:

private void NormalizeSelectorRoutes(ActionModel action)
{
    foreach (var selector in action.Selectors)
    {
        if (selector.AttributeRouteModel == null)
        {
            selector.AttributeRouteModel = new AttributeRouteModel(new RouteAttribute(CalculateRouteTemplate(action)));
        }

        if (selector.ActionConstraints.OfType<HttpMethodActionConstraint>().FirstOrDefault()?.HttpMethods?.FirstOrDefault() == null)
        {
            selector.ActionConstraints.Add(new HttpMethodActionConstraint(new[] { GetHttpMethod(action) }));
        }
    }
}

沒有路由就給它補路由,沒有約束就給它補約束。


現在我們的 ApplicationServiceConvention 的代碼應該如下:

using Microsoft.AspNetCore.Mvc;
using Microsoft.AspNetCore.Mvc.ActionConstraints;
using Microsoft.AspNetCore.Mvc.ApplicationModels;
using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;

namespace SimpleDynamicWebAPI
{
    public class ApplicationServiceConvention : IApplicationModelConvention
    {
        public void Apply(ApplicationModel application)
        {
            foreach (var controller in application.Controllers)
            {
                if (typeof(IApplicationService).IsAssignableFrom(controller.ControllerType))
                {
                    ConfigureApplicationService(controller);
                }
            }
        }

        private void ConfigureApplicationService(ControllerModel controller)
        {
            ConfigureApiExplorer(controller);
            ConfigureSelector(controller);
            ConfigureParameters(controller);
        }

        private void ConfigureApiExplorer(ControllerModel controller)
        {
            if (!controller.ApiExplorer.IsVisible.HasValue)
            {
                controller.ApiExplorer.IsVisible = true;
            }

            foreach (var action in controller.Actions)
            {
                if (!action.ApiExplorer.IsVisible.HasValue)
                {
                    action.ApiExplorer.IsVisible = true;
                }
            }
        }

        private void ConfigureSelector(ControllerModel controller)
        {
            RemoveEmptySelectors(controller.Selectors);

            if (controller.Selectors.Any(temp => temp.AttributeRouteModel != null))
            {
                return;
            }

            foreach (var action in controller.Actions)
            {
                ConfigureSelector(action);
            }
        }

        private void ConfigureSelector(ActionModel action)
        {
            RemoveEmptySelectors(action.Selectors);

            if (action.Selectors.Count <= 0)
            {
                AddApplicationServiceSelector(action);
            }
            else
            {
                NormalizeSelectorRoutes(action);
            }
        }

        private void ConfigureParameters(ControllerModel controller)
        {
            throw new NotImplementedException();
        }

        private void NormalizeSelectorRoutes(ActionModel action)
        {
            foreach (var selector in action.Selectors)
            {
                if (selector.AttributeRouteModel == null)
                {
                    selector.AttributeRouteModel = new AttributeRouteModel(new RouteAttribute(CalculateRouteTemplate(action)));
                }

                if (selector.ActionConstraints.OfType<HttpMethodActionConstraint>().FirstOrDefault()?.HttpMethods?.FirstOrDefault() == null)
                {
                    selector.ActionConstraints.Add(new HttpMethodActionConstraint(new[] { GetHttpMethod(action) }));
                }
            }
        }

        private void AddApplicationServiceSelector(ActionModel action)
        {
            var selector = new SelectorModel();
            selector.AttributeRouteModel = 	   

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

-Advertisement-
Play Games
更多相關文章
  • 記錄webapi日誌我使用了兩種辦法,一種是存儲TXT的log文檔,但我發現使用使用了我介面的日誌都會存儲到我電腦上。後面改用資料庫存儲log。資料庫存儲log信息這種方法個人比較推薦。之前花費了一些時間來寫TXT存儲還是想記錄下來。 轉載自:https://blog.csdn.net/lordwi ...
  • 打開Web.config,修改兩處。 <system.web> <httpRuntime maxUrlLength="109999" maxQueryStringLength="2097151" /> </system.web> <system.webServer> <security> <requ ...
  • 場景 包含工具類部分如下: CSV文件轉換;DataTable轉實體;Excel操作類;FTP操作類;Html操作類;IP輔助類;JSON操作;JS操作;URL的操作類;XML操作類;處理多媒體的公共類;彈出消息類;二維碼操作類;漢字轉拼音;加密解密;科學計數,數學;類型轉換;配置文件操作類;上傳下 ...
  • 生成的單個文件的壓縮率可達50%,忍不住要壓縮一下,當然下載的時候也需要解壓,我用的是GZipStream 1.壓縮 compressionStream.Flush(); 這很重要,否則msTarget.ToArray()會丟掉部分內容。 2.解壓 ...
  • 一、webapi創建 1、創建項目 我使用的是VS2015,點開新建項目,安裝如下操作執行: . 2、設置路由 config.Routes.MapHttpRoute( name: "DefaultApi", routeTemplate: "api/{controller}/{action}/{id} ...
  • 發送請求時出現400錯誤,錯誤代碼為:webResponse = (HttpWebResponse)webRequest.GetResponse(); 解決辦法:拋出異常 HttpWebResponse webResponse; try { webResponse = (HttpWebRespons ...
  • 前言 這幾天研究了一下 vJoy 這個虛擬游戲手柄驅動,感覺挺好玩的。但是使用時發現一個問題,C# SDK 中的程式集被分為 x86 和 x64 兩個版本,如果直接在 AnyCPU 平臺編譯運行就有隱患,在32位系統中運行程式時會因為程式集版本不相容而崩潰。這個 SDK 的兩個版本文件名完全相同,根 ...
  • str jsonstr ='{"FieldValues":[{"FieldName":"Field1","FieldValue":"Rec1Field1Value"},'+ '{"FieldName":"Field2","FieldValue":"Rec1Field2Value"}]}'; Map ...
一周排行
    -Advertisement-
    Play Games
  • 移動開發(一):使用.NET MAUI開發第一個安卓APP 對於工作多年的C#程式員來說,近來想嘗試開發一款安卓APP,考慮了很久最終選擇使用.NET MAUI這個微軟官方的框架來嘗試體驗開發安卓APP,畢竟是使用Visual Studio開發工具,使用起來也比較的順手,結合微軟官方的教程進行了安卓 ...
  • 前言 QuestPDF 是一個開源 .NET 庫,用於生成 PDF 文檔。使用了C# Fluent API方式可簡化開發、減少錯誤並提高工作效率。利用它可以輕鬆生成 PDF 報告、發票、導出文件等。 項目介紹 QuestPDF 是一個革命性的開源 .NET 庫,它徹底改變了我們生成 PDF 文檔的方 ...
  • 項目地址 項目後端地址: https://github.com/ZyPLJ/ZYTteeHole 項目前端頁面地址: ZyPLJ/TreeHoleVue (github.com) https://github.com/ZyPLJ/TreeHoleVue 目前項目測試訪問地址: http://tree ...
  • 話不多說,直接開乾 一.下載 1.官方鏈接下載: https://www.microsoft.com/zh-cn/sql-server/sql-server-downloads 2.在下載目錄中找到下麵這個小的安裝包 SQL2022-SSEI-Dev.exe,運行開始下載SQL server; 二. ...
  • 前言 隨著物聯網(IoT)技術的迅猛發展,MQTT(消息隊列遙測傳輸)協議憑藉其輕量級和高效性,已成為眾多物聯網應用的首選通信標準。 MQTTnet 作為一個高性能的 .NET 開源庫,為 .NET 平臺上的 MQTT 客戶端與伺服器開發提供了強大的支持。 本文將全面介紹 MQTTnet 的核心功能 ...
  • Serilog支持多種接收器用於日誌存儲,增強器用於添加屬性,LogContext管理動態屬性,支持多種輸出格式包括純文本、JSON及ExpressionTemplate。還提供了自定義格式化選項,適用於不同需求。 ...
  • 目錄簡介獲取 HTML 文檔解析 HTML 文檔測試參考文章 簡介 動態內容網站使用 JavaScript 腳本動態檢索和渲染數據,爬取信息時需要模擬瀏覽器行為,否則獲取到的源碼基本是空的。 本文使用的爬取步驟如下: 使用 Selenium 獲取渲染後的 HTML 文檔 使用 HtmlAgility ...
  • 1.前言 什麼是熱更新 游戲或者軟體更新時,無需重新下載客戶端進行安裝,而是在應用程式啟動的情況下,在內部進行資源或者代碼更新 Unity目前常用熱更新解決方案 HybridCLR,Xlua,ILRuntime等 Unity目前常用資源管理解決方案 AssetBundles,Addressable, ...
  • 本文章主要是在C# ASP.NET Core Web API框架實現向手機發送驗證碼簡訊功能。這裡我選擇是一個互億無線簡訊驗證碼平臺,其實像阿裡雲,騰訊雲上面也可以。 首先我們先去 互億無線 https://www.ihuyi.com/api/sms.html 去註冊一個賬號 註冊完成賬號後,它會送 ...
  • 通過以下方式可以高效,並保證數據同步的可靠性 1.API設計 使用RESTful設計,確保API端點明確,並使用適當的HTTP方法(如POST用於創建,PUT用於更新)。 設計清晰的請求和響應模型,以確保客戶端能夠理解預期格式。 2.數據驗證 在伺服器端進行嚴格的數據驗證,確保接收到的數據符合預期格 ...