public class ControllerActionInvoker : ResourceInvoker, IActionInvoker

  我们知道,ControllerActionInvoker实现了IActionInvoker接口。然而却找不到InvokeAsync的方法实现。检查以后发现,它把实现放到了抽象类ResourceInvoker中了。那好吧,我们先来看看这个ResourceInvoker。

        public virtual async Task InvokeAsync()
{
try
{
_diagnosticSource.BeforeAction(
_actionContext.ActionDescriptor,
_actionContext.HttpContext,
_actionContext.RouteData); using (_logger.ActionScope(_actionContext.ActionDescriptor))
{
_logger.ExecutingAction(_actionContext.ActionDescriptor); var startTimestamp = _logger.IsEnabled(LogLevel.Information) ? Stopwatch.GetTimestamp() : ; try
{
//核心步骤
await InvokeFilterPipelineAsync();
}
finally
{
ReleaseResources();
_logger.ExecutedAction(_actionContext.ActionDescriptor, startTimestamp);
}
}
}
finally
{
_diagnosticSource.AfterAction(
_actionContext.ActionDescriptor,
_actionContext.HttpContext,
_actionContext.RouteData);
}
}

  事实上,核心步骤就是执行过滤器管道。其他都是日志打印和诊断的内容。

private async Task InvokeFilterPipelineAsync()
{
var next = State.InvokeBegin; // The `scope` tells the `Next` method who the caller is, and what kind of state to initialize to
// communicate a result. The outermost scope is `Scope.Invoker` and doesn't require any type
// of context or result other than throwing.
var scope = Scope.Invoker; // The `state` is used for internal state handling during transitions between states. In practice this
// means storing a filter instance in `state` and then retrieving it in the next state.
var state = (object)null; // `isCompleted` will be set to true when we've reached a terminal state.
var isCompleted = false; //循环执行所有步骤
while (!isCompleted)
{
await Next(ref next, ref scope, ref state, ref isCompleted);
}
}

  关键在于最后一个循环,其次是Next方法。这个方法比较长,我们一段一段来看。

private Task Next(ref State next, ref Scope scope, ref object state, ref bool isCompleted)
{
switch (next)
{
case State.InvokeBegin:
{
goto case State.AuthorizationBegin;
} case State.AuthorizationBegin:
{
_cursor.Reset();
goto case State.AuthorizationNext;
} case State.AuthorizationNext:
{
var current = _cursor.GetNextFilter<IAuthorizationFilter, IAsyncAuthorizationFilter>();
if (current.FilterAsync != null)
{
if (_authorizationContext == null)
{
_authorizationContext = new AuthorizationFilterContext(_actionContext, _filters);
} state = current.FilterAsync;
goto case State.AuthorizationAsyncBegin;
}
else if (current.Filter != null)
{
if (_authorizationContext == null)
{
_authorizationContext = new AuthorizationFilterContext(_actionContext, _filters);
} state = current.Filter;
goto case State.AuthorizationSync;
}
else
{
goto case State.AuthorizationEnd;
}
}

  我们看到,首先是一个大的Switch语句。Begin之后紧跟的就是Authorization,也就是说,整个过滤器管道第一步就是授权。但这里出现的都是一些前期准备。

 case State.AuthorizationAsyncBegin:
{
Debug.Assert(state != null);
Debug.Assert(_authorizationContext != null); var filter = (IAsyncAuthorizationFilter)state;
var authorizationContext = _authorizationContext; _diagnosticSource.BeforeOnAuthorizationAsync(authorizationContext, filter); var task = filter.OnAuthorizationAsync(authorizationContext);
if (task.Status != TaskStatus.RanToCompletion)
{
next = State.AuthorizationAsyncEnd;
return task;
} goto case State.AuthorizationAsyncEnd;
} case State.AuthorizationAsyncEnd:
{
Debug.Assert(state != null);
Debug.Assert(_authorizationContext != null); var filter = (IAsyncAuthorizationFilter)state;
var authorizationContext = _authorizationContext; _diagnosticSource.AfterOnAuthorizationAsync(authorizationContext, filter); if (authorizationContext.Result != null)
{
goto case State.AuthorizationShortCircuit;
} goto case State.AuthorizationNext;
} case State.AuthorizationSync:
{
Debug.Assert(state != null);
Debug.Assert(_authorizationContext != null); var filter = (IAuthorizationFilter)state;
var authorizationContext = _authorizationContext; _diagnosticSource.BeforeOnAuthorization(authorizationContext, filter); filter.OnAuthorization(authorizationContext); _diagnosticSource.AfterOnAuthorization(authorizationContext, filter); if (authorizationContext.Result != null)
{
goto case State.AuthorizationShortCircuit;
} goto case State.AuthorizationNext;
} case State.AuthorizationShortCircuit:
{
Debug.Assert(state != null);
Debug.Assert(_authorizationContext != null); _logger.AuthorizationFailure((IFilterMetadata)state); // If an authorization filter short circuits, the result is the last thing we execute
// so just return that task instead of calling back into the state machine.
isCompleted = true;
return InvokeResultAsync(_authorizationContext.Result);
} case State.AuthorizationEnd:
{
goto case State.ResourceBegin;
}

  我们看到,这里承接上面的步骤,实际执行了授权过滤器。接下来,是资源过滤器:

case State.ResourceBegin:
{
_cursor.Reset();
goto case State.ResourceNext;
} case State.ResourceNext:
{
var current = _cursor.GetNextFilter<IResourceFilter, IAsyncResourceFilter>();
if (current.FilterAsync != null)
{
if (_resourceExecutingContext == null)
{
_resourceExecutingContext = new ResourceExecutingContext(
_actionContext,
_filters,
_valueProviderFactories);
} state = current.FilterAsync;
goto case State.ResourceAsyncBegin;
}
else if (current.Filter != null)
{
if (_resourceExecutingContext == null)
{
_resourceExecutingContext = new ResourceExecutingContext(
_actionContext,
_filters,
_valueProviderFactories);
} state = current.Filter;
goto case State.ResourceSyncBegin;
}
else
{
// All resource filters are currently on the stack - now execute the 'inside'.
goto case State.ResourceInside;
}
} case State.ResourceAsyncBegin:
{
Debug.Assert(state != null);
Debug.Assert(_resourceExecutingContext != null); var filter = (IAsyncResourceFilter)state;
var resourceExecutingContext = _resourceExecutingContext; _diagnosticSource.BeforeOnResourceExecution(resourceExecutingContext, filter); var task = filter.OnResourceExecutionAsync(resourceExecutingContext, InvokeNextResourceFilterAwaitedAsync);
if (task.Status != TaskStatus.RanToCompletion)
{
next = State.ResourceAsyncEnd;
return task;
} goto case State.ResourceAsyncEnd;
} case State.ResourceAsyncEnd:
{
Debug.Assert(state != null);
Debug.Assert(_resourceExecutingContext != null); var filter = (IAsyncResourceFilter)state;
if (_resourceExecutedContext == null)
{
// If we get here then the filter didn't call 'next' indicating a short circuit.
_resourceExecutedContext = new ResourceExecutedContext(_resourceExecutingContext, _filters)
{
Canceled = true,
Result = _resourceExecutingContext.Result,
}; _diagnosticSource.AfterOnResourceExecution(_resourceExecutedContext, filter); // A filter could complete a Task without setting a result
if (_resourceExecutingContext.Result != null)
{
goto case State.ResourceShortCircuit;
}
} goto case State.ResourceEnd;
} case State.ResourceSyncBegin:
{
Debug.Assert(state != null);
Debug.Assert(_resourceExecutingContext != null); var filter = (IResourceFilter)state;
var resourceExecutingContext = _resourceExecutingContext; _diagnosticSource.BeforeOnResourceExecuting(resourceExecutingContext, filter); filter.OnResourceExecuting(resourceExecutingContext); _diagnosticSource.AfterOnResourceExecuting(resourceExecutingContext, filter); if (resourceExecutingContext.Result != null)
{
_resourceExecutedContext = new ResourceExecutedContext(resourceExecutingContext, _filters)
{
Canceled = true,
Result = _resourceExecutingContext.Result,
}; goto case State.ResourceShortCircuit;
} var task = InvokeNextResourceFilter();
if (task.Status != TaskStatus.RanToCompletion)
{
next = State.ResourceSyncEnd;
return task;
} goto case State.ResourceSyncEnd;
} case State.ResourceSyncEnd:
{
Debug.Assert(state != null);
Debug.Assert(_resourceExecutingContext != null);
Debug.Assert(_resourceExecutedContext != null); var filter = (IResourceFilter)state;
var resourceExecutedContext = _resourceExecutedContext; _diagnosticSource.BeforeOnResourceExecuted(resourceExecutedContext, filter); filter.OnResourceExecuted(resourceExecutedContext); _diagnosticSource.AfterOnResourceExecuted(resourceExecutedContext, filter); goto case State.ResourceEnd;
} case State.ResourceShortCircuit:
{
Debug.Assert(state != null);
Debug.Assert(_resourceExecutingContext != null);
Debug.Assert(_resourceExecutedContext != null); _logger.ResourceFilterShortCircuited((IFilterMetadata)state); var task = InvokeResultAsync(_resourceExecutingContext.Result);
if (task.Status != TaskStatus.RanToCompletion)
{
next = State.ResourceEnd;
return task;
} goto case State.ResourceEnd;
} case State.ResourceInside:
{
goto case State.ExceptionBegin;
}

  可以看到,整个流程和授权过滤器的流程非常相似。接下来是异常过滤器:

 case State.ExceptionBegin:
{
_cursor.Reset();
goto case State.ExceptionNext;
} case State.ExceptionNext:
{
var current = _cursor.GetNextFilter<IExceptionFilter, IAsyncExceptionFilter>();
if (current.FilterAsync != null)
{
state = current.FilterAsync;
goto case State.ExceptionAsyncBegin;
}
else if (current.Filter != null)
{
state = current.Filter;
goto case State.ExceptionSyncBegin;
}
else if (scope == Scope.Exception)
{
// All exception filters are on the stack already - so execute the 'inside'.
goto case State.ExceptionInside;
}
else
{
// There are no exception filters - so jump right to the action.
Debug.Assert(scope == Scope.Invoker || scope == Scope.Resource);
goto case State.ActionBegin;
}
} case State.ExceptionAsyncBegin:
{
var task = InvokeNextExceptionFilterAsync();
if (task.Status != TaskStatus.RanToCompletion)
{
next = State.ExceptionAsyncResume;
return task;
} goto case State.ExceptionAsyncResume;
} case State.ExceptionAsyncResume:
{
Debug.Assert(state != null); var filter = (IAsyncExceptionFilter)state;
var exceptionContext = _exceptionContext; // When we get here we're 'unwinding' the stack of exception filters. If we have an unhandled exception,
// we'll call the filter. Otherwise there's nothing to do.
if (exceptionContext?.Exception != null && !exceptionContext.ExceptionHandled)
{
_diagnosticSource.BeforeOnExceptionAsync(exceptionContext, filter); var task = filter.OnExceptionAsync(exceptionContext);
if (task.Status != TaskStatus.RanToCompletion)
{
next = State.ExceptionAsyncEnd;
return task;
} goto case State.ExceptionAsyncEnd;
} goto case State.ExceptionEnd;
} case State.ExceptionAsyncEnd:
{
Debug.Assert(state != null);
Debug.Assert(_exceptionContext != null); var filter = (IAsyncExceptionFilter)state;
var exceptionContext = _exceptionContext; _diagnosticSource.AfterOnExceptionAsync(exceptionContext, filter); if (exceptionContext.Exception == null || exceptionContext.ExceptionHandled)
{
// We don't need to do anthing to trigger a short circuit. If there's another
// exception filter on the stack it will check the same set of conditions
// and then just skip itself.
_logger.ExceptionFilterShortCircuited(filter);
} goto case State.ExceptionEnd;
} case State.ExceptionSyncBegin:
{
var task = InvokeNextExceptionFilterAsync();
if (task.Status != TaskStatus.RanToCompletion)
{
next = State.ExceptionSyncEnd;
return task;
} goto case State.ExceptionSyncEnd;
} case State.ExceptionSyncEnd:
{
Debug.Assert(state != null); var filter = (IExceptionFilter)state;
var exceptionContext = _exceptionContext; // When we get here we're 'unwinding' the stack of exception filters. If we have an unhandled exception,
// we'll call the filter. Otherwise there's nothing to do.
if (exceptionContext?.Exception != null && !exceptionContext.ExceptionHandled)
{
_diagnosticSource.BeforeOnException(exceptionContext, filter); filter.OnException(exceptionContext); _diagnosticSource.AfterOnException(exceptionContext, filter); if (exceptionContext.Exception == null || exceptionContext.ExceptionHandled)
{
// We don't need to do anthing to trigger a short circuit. If there's another
// exception filter on the stack it will check the same set of conditions
// and then just skip itself.
_logger.ExceptionFilterShortCircuited(filter);
}
} goto case State.ExceptionEnd;
} case State.ExceptionInside:
{
goto case State.ActionBegin;
} case State.ExceptionHandled:
{
// We arrive in this state when an exception happened, but was handled by exception filters
// either by setting ExceptionHandled, or nulling out the Exception or setting a result
// on the ExceptionContext.
//
// We need to execute the result (if any) and then exit gracefully which unwinding Resource
// filters. Debug.Assert(state != null);
Debug.Assert(_exceptionContext != null); if (_exceptionContext.Result == null)
{
_exceptionContext.Result = new EmptyResult();
} if (scope == Scope.Resource)
{
Debug.Assert(_exceptionContext.Result != null);
_result = _exceptionContext.Result;
} var task = InvokeResultAsync(_exceptionContext.Result);
if (task.Status != TaskStatus.RanToCompletion)
{
next = State.ResourceInsideEnd;
return task;
} goto case State.ResourceInsideEnd;
} case State.ExceptionEnd:
{
var exceptionContext = _exceptionContext; if (scope == Scope.Exception)
{
isCompleted = true;
return Task.CompletedTask;
} if (exceptionContext != null)
{
if (exceptionContext.Result != null ||
exceptionContext.Exception == null ||
exceptionContext.ExceptionHandled)
{
goto case State.ExceptionHandled;
} Rethrow(exceptionContext);
Debug.Fail("unreachable");
} goto case State.ResultBegin;
}

  接下来是动作过滤器:

case State.ActionBegin:
{
var task = InvokeInnerFilterAsync();
if (task.Status != TaskStatus.RanToCompletion)
{
next = State.ActionEnd;
return task;
} goto case State.ActionEnd;
} case State.ActionEnd:
{
if (scope == Scope.Exception)
{
// If we're inside an exception filter, let's allow those filters to 'unwind' before
// the result.
isCompleted = true;
return Task.CompletedTask;
} Debug.Assert(scope == Scope.Invoker || scope == Scope.Resource);
goto case State.ResultBegin;
}

  然后是结果过滤器:

case State.ResultBegin:
{
_cursor.Reset();
goto case State.ResultNext;
} case State.ResultNext:
{
var current = _cursor.GetNextFilter<IResultFilter, IAsyncResultFilter>();
if (current.FilterAsync != null)
{
if (_resultExecutingContext == null)
{
_resultExecutingContext = new ResultExecutingContext(_actionContext, _filters, _result, _instance);
} state = current.FilterAsync;
goto case State.ResultAsyncBegin;
}
else if (current.Filter != null)
{
if (_resultExecutingContext == null)
{
_resultExecutingContext = new ResultExecutingContext(_actionContext, _filters, _result, _instance);
} state = current.Filter;
goto case State.ResultSyncBegin;
}
else
{
goto case State.ResultInside;
}
} case State.ResultAsyncBegin:
{
Debug.Assert(state != null);
Debug.Assert(_resultExecutingContext != null); var filter = (IAsyncResultFilter)state;
var resultExecutingContext = _resultExecutingContext; _diagnosticSource.BeforeOnResultExecution(resultExecutingContext, filter); var task = filter.OnResultExecutionAsync(resultExecutingContext, InvokeNextResultFilterAwaitedAsync);
if (task.Status != TaskStatus.RanToCompletion)
{
next = State.ResultAsyncEnd;
return task;
} goto case State.ResultAsyncEnd;
} case State.ResultAsyncEnd:
{
Debug.Assert(state != null);
Debug.Assert(_resultExecutingContext != null); var filter = (IAsyncResultFilter)state;
var resultExecutingContext = _resultExecutingContext;
var resultExecutedContext = _resultExecutedContext; if (resultExecutedContext == null || resultExecutingContext.Cancel)
{
// Short-circuited by not calling next || Short-circuited by setting Cancel == true
_logger.ResultFilterShortCircuited(filter); _resultExecutedContext = new ResultExecutedContext(
_actionContext,
_filters,
resultExecutingContext.Result,
_instance)
{
Canceled = true,
};
} _diagnosticSource.AfterOnResultExecution(_resultExecutedContext, filter);
goto case State.ResultEnd;
} case State.ResultSyncBegin:
{
Debug.Assert(state != null);
Debug.Assert(_resultExecutingContext != null); var filter = (IResultFilter)state;
var resultExecutingContext = _resultExecutingContext; _diagnosticSource.BeforeOnResultExecuting(resultExecutingContext, filter); filter.OnResultExecuting(resultExecutingContext); _diagnosticSource.AfterOnResultExecuting(resultExecutingContext, filter); if (_resultExecutingContext.Cancel)
{
// Short-circuited by setting Cancel == true
_logger.ResultFilterShortCircuited(filter); _resultExecutedContext = new ResultExecutedContext(
resultExecutingContext,
_filters,
resultExecutingContext.Result,
_instance)
{
Canceled = true,
}; goto case State.ResultEnd;
} var task = InvokeNextResultFilterAsync();
if (task.Status != TaskStatus.RanToCompletion)
{
next = State.ResultSyncEnd;
return task;
} goto case State.ResultSyncEnd;
} case State.ResultSyncEnd:
{
Debug.Assert(state != null);
Debug.Assert(_resultExecutingContext != null);
Debug.Assert(_resultExecutedContext != null); var filter = (IResultFilter)state;
var resultExecutedContext = _resultExecutedContext; _diagnosticSource.BeforeOnResultExecuted(resultExecutedContext, filter); filter.OnResultExecuted(resultExecutedContext); _diagnosticSource.AfterOnResultExecuted(resultExecutedContext, filter); goto case State.ResultEnd;
}

  接下来,我们需要重点关注IActionResult如何执行的:

    case State.ResultInside:
{
// If we executed result filters then we need to grab the result from there.
if (_resultExecutingContext != null)
{
_result = _resultExecutingContext.Result;
} if (_result == null)
{
// The empty result is always flowed back as the 'executed' result if we don't have one.
_result = new EmptyResult();
} //执行结果,关键步骤
var task = InvokeResultAsync(_result);
if (task.Status != TaskStatus.RanToCompletion)
{
next = State.ResultEnd;
return task;
} goto case State.ResultEnd;
} case State.ResultEnd:
{
var result = _result; if (scope == Scope.Result)
{
if (_resultExecutedContext == null)
{
_resultExecutedContext = new ResultExecutedContext(_actionContext, _filters, result, _instance);
} isCompleted = true;
return Task.CompletedTask;
} Rethrow(_resultExecutedContext); goto case State.ResourceInsideEnd;
}

  我们来看看这个InvokeResultAsync方法,实质上就是调用了IActionResult的ExecuteResultAsync方法。

protected async Task InvokeResultAsync(IActionResult result)
{
var actionContext = _actionContext; _diagnosticSource.BeforeActionResult(actionContext, result); try
{
//这里的步骤
await result.ExecuteResultAsync(actionContext);
}
finally
{
_diagnosticSource.AfterActionResult(actionContext, result);
}
}

  这就是ResourceInvokier的主要内容。还有一点重要的是几个抽象方法,它们会在ControllerActionInvoker中实现。

protected abstract void ReleaseResources();
protected abstract Task InvokeInnerFilterAsync();

ASP.NET Core学习总结(2)的更多相关文章

  1. ASP.NET Core学习系列

    .NET Core ASP.NET Core ASP.NET Core学习之一 入门简介 ASP.NET Core学习之二 菜鸟踩坑 ASP.NET Core学习之三 NLog日志 ASP.NET C ...

  2. WebAPI调用笔记 ASP.NET CORE 学习之自定义异常处理 MySQL数据库查询优化建议 .NET操作XML文件之泛型集合的序列化与反序列化 Asp.Net Core 轻松学-多线程之Task快速上手 Asp.Net Core 轻松学-多线程之Task(补充)

    WebAPI调用笔记   前言 即时通信项目中初次调用OA接口遇到了一些问题,因为本人从业后几乎一直做CS端项目,一个简单的WebAPI调用居然浪费了不少时间,特此记录. 接口描述 首先说明一下,基于 ...

  3. ASP.NET Core学习指导

    ASP.NET Core 学习指导 "工欲善其事必先利其器".我们在做事情之前,总应该做好充分的准备,熟悉自己的工具.就像玩游戏有一些最低配置一样,学习一个新的框架,也需要有一些基 ...

  4. Asp.Net Core学习笔记:入门篇

    Asp.Net Core 学习 基于.Net Core 2.2版本的学习笔记. 常识 像Django那样自动检查代码更新,自动重载服务器(太方便了) dotnet watch run 托管设置 设置项 ...

  5. ASP.NET Core学习零散记录

    赶着潮流听着歌,学着.net玩着Core 竹子学Core,目前主要看老A(http://www.cnblogs.com/artech/)和tom大叔的博客(http://www.cnblogs.com ...

  6. ASP.NET Core学习之三 NLog日志

    上一篇简单介绍了日志的使用方法,也仅仅是用来做下学习,更何况只能在console输出. NLog已是日志库的一员大佬,使用也简单方便,本文介绍的环境是居于.NET CORE 2.0 ,目前的版本也只有 ...

  7. ASP.NET Core学习之一 入门简介

    一.入门简介 在学习之前,要先了解ASP.NET Core是什么?为什么?很多人学习新技术功利心很重,恨不得立马就学会了. 其实,那样做很不好,马马虎虎,联系过程中又花费非常多的时间去解决所遇到的“问 ...

  8. ASP.NET Core学习总结(1)

    经过那么长时间的学习,终于想给自己这段时间的学习工作做个总结了.记得刚开始学习的时候,什么资料都没有,光就啃文档.不过,值得庆幸的是,自己总算还有一些Web开发的基础.至少ASP.NET的WebFor ...

  9. Asp.net Core学习笔记

    之前记在github上的,现在搬运过来 变化还是很大的,感觉和Nodejs有点类似,比如中间件的使用 ,努力学习ing... 优点 不依赖IIS 开源和跨平台 中间件支持 性能优化 无所不在的依赖注入 ...

  10. 2019年ASP.NET Core学习路线

    - [先决条件] + C# + Entity Framework + ASP.NET Core + SQL 基础知识 - [通用开发技能] + 学习 GIT, 在 GitHub 中创建开源项目 + 掌 ...

随机推荐

  1. django-中间件,流量统计实例

    Django中间件(Middleware) 中间件,顾名思义,就是处在中间的一些软件.比如匹配到了URL,但是还没有执行view函数的时候,这个时候可以执行一些代码,这个代码就是中间件. HttpRe ...

  2. Tessnet2 a .NET 2.0 Open Source OCR assembly using Tesseract engine

    http://www.pixel-technology.com/freeware/tessnet2/ Tessnet2 a .NET 2.0 Open Source OCR assembly usin ...

  3. 在MarkDown中插入数学公式对照表(持续更新)

    目录 在MarkDown中可以插入数学公式,但是在博客园和有道云笔记之中的数学公式插入方式略有不同(博客园需要先在后台选项中开启插入数学公式选项): 代码 行内公式 整行公式 博客园 $数学公式$ $ ...

  4. 判断viewpager左右滑动方向

    实现思路就是通过viewpager的滑动监听,用参数position进行比较,同时当判断完这个要把比较的positon覆盖.这里简单介绍一下public void onPageScrolled(int ...

  5. PEAR

    简介:pear是php扩展与应用库(the php extension and application repository)的缩写.它是一个php扩展及应用的一个代码仓库. 编码规范:参考(http ...

  6. ICG游戏:证明,先手不是必胜就是必败。

    简介: ICG游戏:Impartial Combinatorial Games,公平的组合游戏. 以下是定义,来自网络,可能不够严谨: 1.两名选手:2.两名选手轮流行动,每一次行动可以在有限合法操作 ...

  7. win10,python连接mysql报”Can't connect to MySQL server on 'localhost' (10061)”

    一.环境及问题描述 1. 环境 操作系统:win10家庭版,64bit python版本:Python 2.7.15 mysql版本:mysql 5.4.3 2. 问题描述 最近跟公司申请电脑,预装w ...

  8. Linux 下批量创建用户(shell 命令)

    第一种方法: 用shell批量创建用户,分为2中:1,批量创建的用户名无规律 :2.批量创建的用户名有规律首先,来说下批量创建的用户名无规律的shell:先把需要批量创建的用户名用一个文本文档列出来, ...

  9. strops()

    <?php echo strpos("You love php, I love php too!","php"); ?>

  10. vs2012

    https://www.microsoft.com/zh-CN/download/confirmation.aspx?id=36020