且听疯吟 在此记录扯淡的青春

最近沉迷于 vscode 和 powershell 不能自拔,真的是太好用了~
顺便撸了一个小功能,用来直接在 powershell 中用浏览器打开对应 git repository 的地址

食用方法:

  • 在 powershell 中输入 code $PROFILE 来编辑 profile (或者你也可以使用其他的编辑器~
  • 将以下内容添加到 profile 文件结尾并保存
    function Open-GitWeb {
        $r = git remote -v | Select-String -Pattern "(https:\/\/|git@)(?<git>.*)\.git"
        if ($r.Matches.Length -gt 0) {
            $t = "https://" + ($r.Matches[0].Groups |
                Where-Object {$_.Name -eq "git"}).Value.Replace(":", "/")
            Write-Host "gh: openning ",$t,"..." -ForegroundColor "green"
            Start-Process $t
        }
        else
        {
            Write-Host "gh: not a git repository or origin not set correctly." -ForegroundColor "red"
        }
    }
    
    Set-Alias gh Open-GitWeb
    

    也可以从这个 gist 地址获取最新版本

  • 在 powershell 中输入 . $PROFILE 刷新配置文件(类似于 bash 的 source)

  • done! 在 git repository 目录下输入 gh 就可以打开对应的 url 了

问题

先看下面一个简单的 ASP.NET MVC 5 的 demo:

  • model
    public class TestModel
    {
        public List<int> Ints { get; set; }
    }
    
  • controller
    public ActionResult Index()
    {
        var testModel = new TestModel();
        return View(testModel);
    }
    
    [ActionName("Index"), HttpPost]
    public ActionResult Post(TestModel testModel)
    {
        return View(testModel);
    }
    
  • view
    @model Test.Controllers.TestModel
    
    <form action="@Url.Action("Index")" method="post">
    
        @for (var i = 0; i < 10; i++)
        {
            @Html.TextBoxFor(model => model.Ints[i])
        }
    
        <input type="submit" value="Submit" />
    </form>
    

有没有看出什么问题?

View 里面的

@for (var i = 0; i < 10; i++)
{
    Html.TextBoxFor(model => model.Ints[i])
}

Model.Ints 并没有初始化的情况下被使用了。

正常情况下可能会这么写:

@{
    if (Model.Ints == null)
    {
        Model.Ints = new List<int>();
    }
    for (var i = 0; i < Model.Ints.Count; i++)
    {
        @Html.TextBoxFor(model => model.Ints[i])
    }
}

如果我们需要 10 个 input,可能还得费心给 Model.Ints 初始化并添加 10 个 元素。

然而前面的写法真的会报错吗?

其实并不会,it works well.

为什么呢? @Html.TextBoxFor(model => model.Ints[i])Model.Ints 并未初始化的时候就使用了,那么应该会抛出异常才对?

原因

那么,我们来看看为什么没有报错。
这就要从源代码上找原因。

幸好,ASP.NET MVC 已经在 Github 上开源了,地址在这里

  • 我们很容易根据 namespace 找到 Html.TextBoxFor 的实现,参考 https://github.com/ASP-NET-MVC/aspnetwebstack/blob/master/src/System.Web.Mvc/Html/InputExtensions.cs#L425

  • 简略的说,根据方法签名追踪,可以找到 InputHelper 方法,即https://github.com/ASP-NET-MVC/aspnetwebstack/blob/master/src/System.Web.Mvc/Html/InputExtensions.cs#L483

  • 重点在这一段:
    string attemptedValue = (string)htmlHelper
        .GetModelStateValue(fullName, typeof(string));
    tagBuilder.MergeAttribute("value", attemptedValue ?? ((useViewData) 
        ? htmlHelper.EvalString(fullName, format) 
        : valueParameter), isExplicitValue);
    

    如果要报错,那么应该报错在 htmlHelper.GetModelStateValue,因为很明显这是获取 Model.Ints[i] 的值的地方

  • 继续找到 HtmlHelper.GetModelStateValue 方法,即 https://github.com/ASP-NET-MVC/aspnetwebstack/blob/master/src/System.Web.Mvc/HtmlHelper.cs#L391

    internal object GetModelStateValue(string key, Type destinationType)
    {
        ModelState modelState;
        if (ViewData.ModelState.TryGetValue(key, out modelState))
        {
            if (modelState.Value != null)
            {
                return modelState.Value.ConvertTo(destinationType, null /* culture */);
            }
        }
        return null;
    }
    

    重点就在于 ViewData.ModelState.TryGetValue 了,显然 ModelState 主结构是一个 Dictionary 来存储所有的值,这个想必大部分人都知道,所以我们绕了一圈最终找到了这里
    也就是说,实际上是通过 Dictionary.TryGetValue(key, out value) 这样的形式来获取对应的值
    具体到我们的问题,即 i == 0 时,在 ModelState 中寻找 key == "Ints[0]" 的值,当然,其值为 null 并且并不会报错

所以整个流程中并不会因为 Model.Ints 未初始化而报错,因为 Html.TextBoxFor(model => model.Ints[i]) 并不是通过直接访问而是从 expression 数据结构和 ModelState 数据绑定中取值。虽然这背后机制并不复杂,但是这个问题突然冒出来的时候,没有完整看过这部分实现,我也并没有想到这其中的关联。

最后,在使用之前初始化一定是一个好习惯!

附送

其实比起看源码,通过 Visual Studio 来 debug 可能更方便。

那么步骤如下:

  • 找到 Tool -> Options -> Debugging -> General
  • Uncheck Enable Just My Code
  • Check Enable Source Server Support
  • 转到 Tool -> Options -> Debugging -> Symbols
  • Check Microsoft Symbol Servers
  • Add http://referencesource.microsoft.com/symbols
  • Add http://msdl.microsoft.com/download/symbols
  • Add http://srv.symbolsource.org/pdb/Public
  • 我也不知道哪个 symbol server 对你有效,所以就都加上吧~
  • 如果你只需要一部分的 modules,可以选择 Only specified modules,比如添加 System.Web.Mvc.dll

接下来进入调试时,只要右键在当前断点上选择 Step Into Specific 就可以选择进入调试源码了~

最近从 farbox 迁移到了 bitcron,由于 bitcron 不再支持 html 的模板,只好用 jade 重写了一次
顺便整理了下之前写这个模板时碰到的一些细节,也许用得上吧

lang

国内很多网站都是不写这个 lang 属性的,比如 baidu。 而大部分国外网站都会写,Twitter 甚至为每一条推文都加上了 lang 属性
那么写上 lang 属性有什么意义呢?顾名思义,lang 属性声明了内容的语言。更详细的来说,比如:

  • 浏览器可以根据 lang=en 知道当前网页是英文,于是可以问你是否需要开启翻译功能
  • Chrome 在版本 21 之后,开始根据 lang 属性来应用不同的默认字体。这意味着你可以为英语页面和中文、日文等页面设置不同的默认字体
    这个选项没有出现在默认设置里,你可以使用这个 Chrome 扩展来设置:Advanced Font Settings
  • 页面的 lang 属性会影响字体的显示

    比如 思源黑体 中同时包含了中文字体和日文字体,我们知道日文中一部分汉字是相同的,为了节省空间,所以它们被放在了“同一个位置”上

    然而即使是同样的字,也可能在字形上存在不同,比如,如果你安装了思源黑体,那么下面同样的字

    门类门类

    <span lang="ja" style="font-family:'Source Han Sans'">门类</span>
    <span lang="zh-Hans" style="font-family:'Source Han Sans'">门类</span>
    

    显示出来的字形是不一样的
    另外,由于中文字体 fallback 的关系,lang=en 下显示的是 lang=ja 的字形,所以,如果发现网页显示的字形很奇怪,那么看看 lang 属性有没有正确的设置吧~
    可以参考这里来决定选择用哪个 Language Code

meta

  • charset
    千万不要忘记 charset 设置,否则可能会出现莫名其妙的乱码

    一般来说是这样设置的:

    <meta name="Content-Type" content="text/html;charset=utf-8" />
    
  • viewport
    这个就不多说了,如果做了自适应的话,应该都不会忘记这个

    可以参考 https://www.w3schools.com/css/css_rwd_viewport.asp

  • web app icon
    在移动设备上,除了将网站收藏为书签外,现在还有更多快捷访问的方式,比如,固定到桌面等等。所以,相对于 favicon,我们有了更多需要设置的 icon

    比如为 iOS 设置放到主屏幕上的图标:

    <link rel="apple-touch-icon" href="apple-touch-iphone.png" />
    <link rel="apple-touch-icon" sizes="72x72" href="apple-touch-ipad.png" />
    <link rel="apple-touch-icon" sizes="114x114" href="apple-touch-iphone4.png" />
    <link rel="apple-touch-icon" sizes="144x144" href="apple-touch-ipad-retina.png" />
    

    相应的还有 Android,Windows 等等,所以你可能会需要这个网站,一次生成支持所有平台的 html。

待续~