ASP.NET Core 认证与授权[6]:授权策略是怎么执行的?
在上一章中,详细介绍了 ASP.NET Core 中的授权策略,在需要授权时,只需要在对应的Controler或者Action上面打上[Authorize]
特性,并指定要执行的策略名称即可,但是,授权策略是怎么执行的呢?怀着一颗好奇的心,忍不住来探索一下它的执行流程。
目录
在《(上一章》中提到,AuthorizeAttribute
只是一个简单的实现了IAuthorizeData
接口的特性,并且在 ASP.NET Core 授权系统中并没有使用到它。我们知道在认证中,还有一个UseAuthentication
扩展方法来激活认证系统,但是在授权中并没有类似的机制。
这是因为当我们使用[Authorize]
通常是在MVC中,由MVC来负责激活授权系统。本来在这个系列的文章中,我并不想涉及到MVC的知识,但是为了能更好的理解授权系统的执行,就来简单介绍一下MVC中与授权相关的知识。
MVC中的授权
当我们使用MVC时,首先会调用MVC的AddMvc
扩展方法,用来注册一些MVC相关的服务:
public static IMvcBuilder AddMvc(this IServiceCollection services)
{
var builder = services.AddMvcCore();
builder.AddAuthorization();
...
}
public static IMvcCoreBuilder AddAuthorization(this IMvcCoreBuilder builder)
{
AddAuthorizationServices(builder.Services);
return builder;
}
internal static void AddAuthorizationServices(IServiceCollection services)
{
services.AddAuthenticationCore();
services.AddAuthorization();
services.AddAuthorizationPolicyEvaluator();
services.TryAddEnumerable(
ServiceDescriptor.Transient<IApplicationModelProvider, AuthorizationApplicationModelProvider>());
}
在上面AddAuthorizationServices
中的前三个方法都属于 ASP.NET Core 《Security》项目中提供的扩展方法,其中前两个在前面几章已经介绍过了,对于AddAuthorizationPolicyEvaluator
放到后面再来介绍,我们先来看一下MVC中的AuthorizationApplicationModelProvider
。
AuthorizationApplicationModelProvider
在MVC中有一个ApplicationModel
的概念,它用来封装Controller
, Filter
, ApiExplorer
等。对应的,在MVC中还提供了一系列的ApplicationModelProvider来初始化ApplicationModel
的各个部分,而AuthorizationApplicationModelProvider
就是用来初始化与授权相关的部分。
public class AuthorizationApplicationModelProvider : IApplicationModelProvider
{
public void OnProvidersExecuting(ApplicationModelProviderContext context)
{
foreach (var controllerModel in context.Result.Controllers)
{
var controllerModelAuthData = controllerModel.Attributes.OfType<IAuthorizeData>().ToArray();
if (controllerModelAuthData.Length > 0)
{
controllerModel.Filters.Add(GetFilter(_policyProvider, controllerModelAuthData));
}
foreach (var attribute in controllerModel.Attributes.OfType<IAllowAnonymous>())
{
controllerModel.Filters.Add(new AllowAnonymousFilter());
}
foreach (var actionModel in controllerModel.Actions)
{
var actionModelAuthData = actionModel.Attributes.OfType<IAuthorizeData>().ToArray();
if (actionModelAuthData.Length > 0)
{
actionModel.Filters.Add(GetFilter(_policyProvider, actionModelAuthData));
}
foreach (var attribute in actionModel.Attributes.OfType<IAllowAnonymous>())
{
actionModel.Filters.Add(new AllowAnonymousFilter());
}
}
}
}
}
如上,首先查找每个Controller中实现了IAuthorizeData
接口的特性,然后将其转化为AuthorizeFilter
并添加到Controller的Filter集合中,紧接着再查找实现了IAllowAnonymous
接口的特性,将其转化为AllowAnonymousFilter
过滤器也添加到Filter集合中,然后以同样的逻辑查找Action上的特性并添加到Action的Filter集合中。
其中的关键点就是将IAuthorizeData
(也就是通过我们熟悉的[Authorize]
特性)转化为MVC中的AuthorizeFilter
过滤器:
public static AuthorizeFilter GetFilter(IAuthorizationPolicyProvider policyProvider, IEnumerable<IAuthorizeData> authData)
{
if (policyProvider.GetType() == typeof(DefaultAuthorizationPolicyProvider))
{
var policy = AuthorizationPolicy.CombineAsync(policyProvider, authData).GetAwaiter().GetResult();
return new AuthorizeFilter(policy);
}
else
{
return new AuthorizeFilter(policyProvider, authData);
}
}
CombineAsync
在上一章的《AuthorizationPolicy》中已经介绍过了,我们往下看看AuthorizeFilter的实现。
AuthorizeFilter
在MVC中有一个AuthorizeFilter
过滤器,类似我们在ASP.NET 4.x中所熟悉的[Authorize]
,它实现了IAsyncAuthorizationFilter
接口,定义如下:
public class AuthorizeFilter : IAsyncAuthorizationFilter, IFilterFactory
{
public AuthorizeFilter(AuthorizationPolicy policy) {}
public AuthorizeFilter(IAuthorizationPolicyProvider policyProvider, IEnumerable<IAuthorizeData> authorizeData) : this(authorizeData) {}
public AuthorizeFilter(IEnumerable<IAuthorizeData> authorizeData) {}
public IEnumerable<IAuthorizeData> AuthorizeData { get; }
public AuthorizationPolicy Policy { get; }
public virtual async Task OnAuthorizationAsync(AuthorizationFilterContext context)
{
var effectivePolicy = Policy;
if (effectivePolicy == null)
{
effectivePolicy = await AuthorizationPolicy.CombineAsync(PolicyProvider, AuthorizeData);
}
var policyEvaluator = context.HttpContext.RequestServices.GetRequiredService<IPolicyEvaluator>();
var authenticateResult = await policyEvaluator.AuthenticateAsync(effectivePolicy, context.HttpContext);
if (context.Filters.Any(item => item is IAllowAnonymousFilter))
{
return;
}
var authorizeResult = await policyEvaluator.AuthorizeAsync(effectivePolicy, authenticateResult, context.HttpContext, context);
... // 如果授权失败,返回ChallengeResult或ForbidResult
}
}
AuthorizeFilter的OnAuthorizationAsync
方法会在Action执行之前触发,其调用IPolicyEvaluator
来完成授权,将执行流程切回到 ASP.NET Core 授权系统中。关于MVC中IApplicationModelProvider
以及Filter
的概念,在以后MVC系列的文章中再来详细介绍,下面就继续介绍 ASP.NET Core 的授权系统,也就是《Security》项目。
IPolicyEvaluator
IPolicyEvaluator是MVC调用授权系统的入口点,其定义如下:
public interface IPolicyEvaluator
{
Task<AuthenticateResult> AuthenticateAsync(AuthorizationPolicy policy, HttpContext context);
Task<PolicyAuthorizationResult> AuthorizeAsync(AuthorizationPolicy policy, AuthenticateResult authenticationResult, HttpContext context, object resource);
}
在上面介绍的AddMVC
中,调用了AddAuthorizationPolicyEvaluator
扩展方法,它有如下定义:
public static class PolicyServiceCollectionExtensions
{
public static IServiceCollection AddAuthorizationPolicyEvaluator(this IServiceCollection services)
{
services.TryAdd(ServiceDescriptor.Transient<IPolicyEvaluator, PolicyEvaluator>());
return services;
}
}
由此可知IPolicyEvaluator
的默认实现为PolicyEvaluator
,我们就从它入手,来一步一步解剖 ASP.NET Core 授权系统的执行步骤。
在AuthorizeFilter
中,依次调到了AuthenticateAsync
和AuthorizeAsync
方法,我们就一一来看。
AuthenticateAsync(AuthenticationSchemes)
为什么还有一个AuthenticateAsync
方法呢,这不是在认证阶段执行的吗?我们看下它的实现:
public class PolicyEvaluator : IPolicyEvaluator
{
public virtual async Task<AuthenticateResult> AuthenticateAsync(AuthorizationPolicy policy, HttpContext context)
{
if (policy.AuthenticationSchemes != null && policy.AuthenticationSchemes.Count > 0)
{
ClaimsPrincipal newPrincipal = null;
foreach (var scheme in policy.AuthenticationSchemes)
{
var result = await context.AuthenticateAsync(scheme);
if (result != null && result.Succeeded)
{
newPrincipal = SecurityHelper.MergeUserPrincipal(newPrincipal, result.Principal);
}
}
if (newPrincipal != null)
{
context.User = newPrincipal;
return AuthenticateResult.Success(new AuthenticationTicket(newPrincipal, string.Join(";", policy.AuthenticationSchemes)));
}
else
{
context.User = new ClaimsPrincipal(new ClaimsIdentity());
return AuthenticateResult.NoResult();
}
}
return (context.User?.Identity?.IsAuthenticated ?? false)
? AuthenticateResult.Success(new AuthenticationTicket(context.User, "context.User"))
: AuthenticateResult.NoResult();
}
}
在《上一章》中,我们知道在AuthorizationPolicy中有AuthenticationSchemes和IAuthorizationRequirement两个属性,并详细介绍介绍了Requirement,但是没有提到AuthenticationSchemes的调用。
那么,看到这里,也就大概明白了,它与Requirements的执行是完全独立的,并在它之前执行,用于重置Claims,那么为什么要重置呢?
在认证的章节介绍过,在认证阶段,只会执行默认的认证Scheme,context.User
就是使用context.AuthenticateAsync(DefaultAuthenticateScheme)
来赋值的,当我们希望使用非默认的Scheme,或者是想合并多个认证Scheme的Claims时,就需要使用基于Scheme的授权来重置Claims了。
它的实现也很简单,直接使用我们在授权策略中指定的Schemes来依次调用认证服务的AuthenticateAsync
方法,并将生成的Claims合并,最后返回我们熟悉的AuthenticateResult
认证结果。
AuthorizeAsync(Requirements)
接下来再看一下PolicyEvaluator的AuthorizeAsync
方法:
public class PolicyEvaluator : IPolicyEvaluator
{
private readonly IAuthorizationService _authorization;
public PolicyEvaluator(IAuthorizationService authorization)
{
_authorization = authorization;
}
public virtual async Task<PolicyAuthorizationResult> AuthorizeAsync(AuthorizationPolicy policy, AuthenticateResult authenticationResult, HttpContext context, object resource)
{
var result = await _authorization.AuthorizeAsync(context.User, resource, policy);
if (result.Succeeded) return PolicyAuthorizationResult.Success();
return (authenticationResult.Succeeded) ? PolicyAuthorizationResult.Forbid() : PolicyAuthorizationResult.Challenge();
}
}
该方法会根据Requirements来完成授权,具体的实现是通过调用IAuthorizationService
来实现的。
最终返回的是一个PolicyAuthorizationResult
对象,并在授权失败时,根据认证结果来返回Forbid(未授权)
或Challenge(未登录)
。
public class PolicyAuthorizationResult
{
private PolicyAuthorizationResult() { }
public bool Challenged { get; private set; }
public bool Forbidden { get; private set; }
public bool Succeeded { get; private set; }
}
IAuthorizationService
然后就到了授权的核心对象AuthorizationService
,也可以称为授权的外交官,我们也可以直接在应用代码中调用该对象来实现授权,它有如下定义:
public interface IAuthorizationService
{
Task<AuthorizationResult> AuthorizeAsync(ClaimsPrincipal user, object resource, string policyName);
Task<AuthorizationResult> AuthorizeAsync(ClaimsPrincipal user, object resource, IEnumerable<IAuthorizationRequirement> requirements);
}
在
AuthorizeAsync
中还涉及到一个resource
对象,用来实现面向资源的授权,放在《下一章》中再来介绍,而在本章与《前一章》的示例中,该值均为null
。
ASP.NET Core 中还为IAuthorizationService
提供了几个扩展方法:
public static class AuthorizationServiceExtensions
{
public static Task<AuthorizationResult> AuthorizeAsync(this IAuthorizationService service, ClaimsPrincipal user, string policyName) {}
public static Task<AuthorizationResult> AuthorizeAsync(this IAuthorizationService service, ClaimsPrincipal user, AuthorizationPolicy policy) {}
public static Task<AuthorizationResult> AuthorizeAsync(this IAuthorizationService service, ClaimsPrincipal user, object resource, IAuthorizationRequirement requirement) {}
public static Task<AuthorizationResult> AuthorizeAsync(this IAuthorizationService service, ClaimsPrincipal user, object resource, AuthorizationPolicy policy) {}
}
其默认实现为DefaultAuthorizationService
:
public class DefaultAuthorizationService : IAuthorizationService
{
private readonly AuthorizationOptions _options;
private readonly IAuthorizationHandlerContextFactory _contextFactory;
private readonly IAuthorizationHandlerProvider _handlers;
private readonly IAuthorizationEvaluator _evaluator;
private readonly IAuthorizationPolicyProvider _policyProvider;
public async Task<AuthorizationResult> AuthorizeAsync(ClaimsPrincipal user, object resource, string policyName)
{
var policy = await _policyProvider.GetPolicyAsync(policyName);
return await this.AuthorizeAsync(user, resource, policy);
}
public async Task<AuthorizationResult> AuthorizeAsync(ClaimsPrincipal user, object resource, IEnumerable<IAuthorizationRequirement> requirements)
{
var authContext = _contextFactory.CreateContext(requirements, user, resource);
var handlers = await _handlers.GetHandlersAsync(authContext);
foreach (var handler in handlers)
{
await handler.HandleAsync(authContext);
if (!_options.InvokeHandlersAfterFailure && authContext.HasFailed)
{
break;
}
}
return _evaluator.Evaluate(authContext);
}
}
通过上面代码可以看出,在《上一章》中介绍的授权策略,在这里获取到它的Requirements,后续便不再需要了。而在AuthorizationService
中是通过调用四大核心对象来完成授权,我们一一来看。
IAuthorizationPolicyProvider
由于在[Authorize]
中,我们指定的是策略的名称,因此需要使用IAuthorizationPolicyProvider
来根据名称获取到策略对象,默认实现为DefaultAuthorizationPolicyProvider
:
public class DefaultAuthorizationPolicyProvider : IAuthorizationPolicyProvider
{
private readonly AuthorizationOptions _options;
public Task<AuthorizationPolicy> GetDefaultPolicyAsync()
{
return Task.FromResult(_options.DefaultPolicy);
}
public virtual Task<AuthorizationPolicy> GetPolicyAsync(string policyName)
{
return Task.FromResult(_options.GetPolicy(policyName));
}
}
在上一章中介绍过,我们定义的策略都保存在《AuthorizationOptions》的字典中,因此在这里只是简单的将AuthorizationOptions
中的同名方法异步化。
IAuthorizationHandlerContextFactory
授权上下文是我们接触较多的对象,当我们自定义授权Handler时就会用到它,它是使用简单工厂模式来创建的:
public class DefaultAuthorizationHandlerContextFactory : IAuthorizationHandlerContextFactory
{
public virtual AuthorizationHandlerContext CreateContext(IEnumerable<IAuthorizationRequirement> requirements, ClaimsPrincipal user, object resource)
{
return new AuthorizationHandlerContext(requirements, user, resource);
}
}
授权上下文中主要包含用户的Claims和授权策略的Requirements:
public class AuthorizationHandlerContext
{
private HashSet<IAuthorizationRequirement> _pendingRequirements;
private bool _failCalled;
private bool _succeedCalled;
public AuthorizationHandlerContext(IEnumerable<IAuthorizationRequirement> requirements, ClaimsPrincipal user, object resource)
{
Requirements = requirements; User = user; Resource = resource;
_pendingRequirements = new HashSet<IAuthorizationRequirement>(requirements);
}
public virtual bool HasFailed { get { return _failCalled; } }
public virtual bool HasSucceeded => !_failCalled && _succeedCalled && !_pendingRequirements.Any();
public virtual void Fail()
{
_failCalled = true;
}
public virtual void Succeed(IAuthorizationRequirement requirement)
{
_succeedCalled = true;
_pendingRequirements.Remove(requirement);
}
}
如上,_pendingRequirements
中保存着所有待验证的Requirements,验证成功的Requirement则从中移除。
IAuthorizationHandlerProvider
兜兜转转,终于进入到了授权的最终验证逻辑中了,首先,使用IAuthorizationHandlerProvider
来获取到所有的授权Handler。
IAuthorizationHandlerProvider
的默认实现为DefaultAuthorizationHandlerProvider
:
public class DefaultAuthorizationHandlerProvider : IAuthorizationHandlerProvider
{
private readonly IEnumerable<IAuthorizationHandler> _handlers;
public DefaultAuthorizationHandlerProvider(IEnumerable<IAuthorizationHandler> handlers)
{
_handlers = handlers;
}
public Task<IEnumerable<IAuthorizationHandler>> GetHandlersAsync(AuthorizationHandlerContext context)
=> Task.FromResult(_handlers);
}
在《上一章》中,我们还介绍到,我们定义的Requirement,可以直接实现IAuthorizationHandler
接口,也可以单独定义Handler,但是需要注册到DI系统中去。
在默认的AuthorizationHandlerProvider中,会从DI系统中获取到我们注册的所有Handler,最终调用其HandleAsync
方法。
我们在实现IAuthorizationHandler
接口时,通常是继承自AuthorizationHandler<TRequirement>
来实现,它有如下定义:
public abstract class AuthorizationHandler<TRequirement> : IAuthorizationHandler where TRequirement : IAuthorizationRequirement
{
public virtual async Task HandleAsync(AuthorizationHandlerContext context)
{
foreach (var req in context.Requirements.OfType<TRequirement>())
{
await HandleRequirementAsync(context, req);
}
}
protected abstract Task HandleRequirementAsync(AuthorizationHandlerContext context, TRequirement requirement);
}
如上,首先会在HandleAsync
过滤出与Requirement对匹配的Handler,然后再调用其HandleRequirementAsync
方法。
那我们定义的直接实现IAuthorizationHandler
了接口的Requirement又是如何执行的呢?
在AddAuthorization
扩展方法中可以看到,默认还为IAuthorizationHandler
注册了一个PassThroughAuthorizationHandler
,定义如下:
public class PassThroughAuthorizationHandler : IAuthorizationHandler
{
public async Task HandleAsync(AuthorizationHandlerContext context)
{
foreach (var handler in context.Requirements.OfType<IAuthorizationHandler>())
{
await handler.HandleAsync(context);
}
}
}
它负责调用该策略中所有实现了IAuthorizationHandler
接口的Requirement。
IAuthorizationEvaluator
最后,通过调用IAuthorizationEvaluator
接口,来完成最终的授权结果,默认实现为DefaultAuthorizationEvaluator
:
public class DefaultAuthorizationEvaluator : IAuthorizationEvaluator
{
public AuthorizationResult Evaluate(AuthorizationHandlerContext context)
=> context.HasSucceeded
? AuthorizationResult.Success()
: AuthorizationResult.Failed(context.HasFailed
? AuthorizationFailure.ExplicitFail()
: AuthorizationFailure.Failed(context.PendingRequirements));
}
当我们在一个策略中指定多个Requirement时,只有全部验证通过时,授权上下文中的HasSucceeded
才会为True,而HasFailed
代表授权结果的显式失败。
这里根据授权上下文的验证结果来生成授权结果:
public class AuthorizationResult
{
public bool Succeeded { get; private set; }
public AuthorizationFailure Failure { get; private set; }
public static AuthorizationResult Success() => new AuthorizationResult { Succeeded = true };
public static AuthorizationResult Failed(AuthorizationFailure failure) => new AuthorizationResult { Failure = failure };
public static AuthorizationResult Failed() => new AuthorizationResult { Failure = AuthorizationFailure.ExplicitFail() };
}
public class AuthorizationFailure
{
private AuthorizationFailure() { }
public bool FailCalled { get; private set; }
public IEnumerable<IAuthorizationRequirement> FailedRequirements { get; private set; }
public static AuthorizationFailure ExplicitFail()
{
return new AuthorizationFailure { FailCalled = true, FailedRequirements = new IAuthorizationRequirement[0] };
}
public static AuthorizationFailure Failed(IEnumerable<IAuthorizationRequirement> failed)
=> new AuthorizationFailure { FailedRequirements = failed };
}
整个授权流程的结构大致如下:
总结
通过对 ASP.NET Core 授权系统执行流程的探索,可以看出授权是主要是通过调用IAuthorizationService
来完成的,而授权策略的本质是提供 Requirement ,我们完全可以使用它们两个来完成各种灵活的授权方式,而不用局限于策略。在 ASP.NET Core 中,还提供了基于资源的授权,放在《下一章》中来介绍,并会简单演示一下在一个通用权限管理系统中如何来授权。
ASP.NET Core 认证与授权[6]:授权策略是怎么执行的?的更多相关文章
- ASP.NET Core 认证与授权[2]:Cookie认证
由于HTTP协议是无状态的,但对于认证来说,必然要通过一种机制来保存用户状态,而最常用,也最简单的就是Cookie了,它由浏览器自动保存并在发送请求时自动附加到请求头中.尽管在现代Web应用中,Coo ...
- 【ASP.NET Core】运行原理(4):授权
本系列将分析ASP.NET Core运行原理 [ASP.NET Core]运行原理(1):创建WebHost [ASP.NET Core]运行原理(2):启动WebHost [ASP.NET Core ...
- 使用JWT的ASP.NET CORE令牌身份验证和授权(无Cookie)——第1部分
原文:使用JWT的ASP.NET CORE令牌身份验证和授权(无Cookie)--第1部分 原文链接:https://www.codeproject.com/Articles/5160941/ASP- ...
- ASP.NET Core 认证与授权[1]:初识认证
在ASP.NET 4.X 中,我们最常用的是Forms认证,它既可以用于局域网环境,也可用于互联网环境,有着非常广泛的使用.但是它很难进行扩展,更无法与第三方认证集成,因此,在 ASP.NET Cor ...
- ASP.NET Core 认证与授权[3]:OAuth & OpenID Connect认证
在上一章中,我们了解到,Cookie认证是一种本地认证方式,通常认证与授权都在同一个服务中,也可以使用Cookie共享的方式分开部署,但局限性较大,而如今随着微服务的流行,更加偏向于将以前的单体应用拆 ...
- ASP.NET Core 认证与授权[4]:JwtBearer认证
在现代Web应用程序中,通常会使用Web, WebApp, NativeApp等多种呈现方式,而后端也由以前的Razor渲染HTML,转变为Stateless的RESTFulAPI,因此,我们需要一种 ...
- ASP.NET Core 认证与授权[5]:初识授权
经过前面几章的姗姗学步,我们了解了在 ASP.NET Core 中是如何认证的,终于来到了授权阶段.在认证阶段我们通过用户令牌获取到用户的Claims,而授权便是对这些的Claims的验证,如:是否拥 ...
- Asp.net Core认证和授权:Cookie认证
关于asp.net core 的文章,博客园已经有很多大牛写过了. 这里我只是记录下自己在学习中的点滴和一些不懂的地方 Cookie一般是用户网站授权,当用户访问需要授权(authorization) ...
- ASP.NET Core 认证与授权[1]:初识认证 (笔记)
原文链接: https://www.cnblogs.com/RainingNight/p/introduce-basic-authentication-in-asp-net-core.html 在A ...
随机推荐
- Unity 使用 陀螺仪 实现 《王者荣耀》 登入界面 背景动态效果
在 <王者荣耀> 登入界面 左右上下晃动手机(有些手机不支持)可以看到背景在变化 我使用的是iPhone SE 效果如下: 对比两张图片的左下角 可以看到差异 至于为什么要这么做: 1.使 ...
- NSTimer的问题
iOS开发中,涉及到定时的问题,我们通常使用NSTimer来解决,例如下面的代码. SFClass.h #import <Foundation/Foundation.h> @interfa ...
- win10 uwp 如何让一个集合按照需要的顺序进行排序
虽然这是 C# 的技术,但是我是用在 uwp ,于是就把标题写这个名.有一天,我的小伙伴让我优化一个列表.这个列表是 ListView 他绑定了一个 ObservableCollection 所以需要 ...
- Python学习笔记(七)
Python学习笔记(七): 深浅拷贝 Set-集合 函数 1. 深浅拷贝 1. 浅拷贝-多层嵌套只拷贝第一层 a = [[1,2],3,4] b = a.copy() print(b) # 结果:[ ...
- win10 + python3.6 + VSCode + tensorflow-gpu + keras + cuda8 + cuDN6N环境配置
写在前面的话: 再弄这个之前,我对python也好,tensorflow也好几乎是0认知的,所以配置这个环境的时候,走了不少弯路,整整耗费了一个星期的时间才搭配完整这个环境,简直了...然而最气的是, ...
- Linux 进程状态 概念 Process State Definition
From : http://www.linfo.org/process_state.html 进程状态是指在进程描述符中状态位的值. 进程,也可被称为任务,是指一个程序运行的实例. 一个进程描述符是一 ...
- fio2.1.10--HOWTO
1.0 Overview and history ------------------------ fio was originally written to save me the hassl ...
- SQL&SQLite
注册博客园有一年多了,每次都是来找点资料,从来没有写过点什么,促使我开始写博客的原因主要有两点 一是在查找资料的过程中,经常需要重复的查找某个知识点,一个知识点时间长了之后总是忘记,这样重复的过程却是 ...
- C#读写Shapefile
Shapefile文件是ArcGIS存储矢量要素的标准格式,要读写Shapefile最简单的方法当然是基于ArcObject(或者ArcEngine)开发,不过网上也有一些开源的解译Shapefile ...
- [译]ASP.NET Core 2.0 路由引擎之网址生成
问题 如何在ASP.NET Core 2.0中由路由引擎来生成网址? 答案 新建一个空项目,修改Startup.cs文件,添加MVC服务和中间件: public void ConfigureServi ...