CSHTML/Razor视图的文档说明

5

我正在撰写我们的显示和编辑模板文档(并且,撰写所有CSHTML的文档将会更有益)。话虽如此,我从未见过任何关于文档化CSHTML Razor视图的方法。

例如,我有这个CSHTML编辑模板:MoneyPicker.cshtml

@model Decimal?
@{
    Layout = null;
}
<div class="money-picker" data-money-picker>
    <div class="form-control-wrapper">
        @Html.TextBox(
            "",
            ViewData.TemplateInfo.FormattedModelValue,
            new
            {
                type = "number",
                @class = "form-control money-control",
                placeholder = ViewData.ModelMetadata.Watermark ?? ViewData.ModelMetadata.DisplayName
            } )
    </div>
</div>

我知道这不是C#,但是否有一种方法可以添加XML文档(或等效文档):

    /// <summary>
    /// Displays a text box wrapped with the money-picker wrappers, allowing javascript to show money buttons to pick the value.
    /// </summary>
    /// <param name="Model">A currency value</param>
    /// <remarks>
    /// The input is a text box that has the type "number"
    /// </remarks>

如果没有的话,是否有人找到了一些文档编辑器/显示模板的方法,可以被某种类似于Asp.Net WebApi的api帮助页面的系统所接受?理想情况下,它甚至会更进一步,从父类型开始,并允许您深入到该类型的每个属性编辑器等中,但我只是试图先从小处开始。作为其中的一部分,我也很想生成模板示例,而不必手动编写这些示例,只是为了帮助澄清我所想的想法。

你的意思是什么:“... without having to hard/manually code said examples…”?你想要文档自动生成吗?“As part of this”是什么意思?这是指什么的一部分? - Max
2个回答

1

我也研究过这个问题,但是没有找到关于文档化 View 的方法,现在我只是在 Controller 中记录了 Views:

/// <summary>
/// This is an example of documentation for the View.
/// </summary>
/// <returns>This is what I am returning.</returns>
public ActionResult CreateNewUser()
{
    return View();
}

你可以使用SandCastle(我更喜欢这个)或GhostDoc生成此文档。

当然,您也可以在View中编写普通注释以进行解释。

@* This is a comment *@

0

这是正在进行中的工作,但可能会有所帮助。

背景

一个MVC 5应用程序正在迁移到MVC Core,并且有很多内部开发的HtmlHelpers必须从头开始为MVC Core重新编写[:-(],但是想要尽快开始使用MVC Core,并尽可能少地更改视图。

想法是拥有一个帮助器目录,作为运行MVC Core应用程序,记录Razor使用的文档。

结果

Final catalog display

解决方案

创建一个ViewComponent,它读取当前视图的源代码并将其显示为格式化的HTML,以及实际的HTML输出。

然后只需为每个HtmlHelper创建一个示例的部分视图即可。

在这种情况下,HtmlHelpers根据“ViewRole”生成不同的HTML,该“ViewRole”指定视图是处于显示模式还是编辑模式。

部分视图

@model App.WebLib.AspNetCore.WebApp.Areas.AppHelpers.Models.AppHelpersViewModel

<div class="form-group">
    @Html.LabelTagFor(m => m.TextInput, 4)
    <div class="col-md-8">
        @Html.TextTagFor(Model.ViewRole, m => m.TextInput)
    </div>
</div>

@await Component.InvokeAsync("DisplaySource", new { executingViewPath = ViewContext.ExecutingFilePath, viewRole = Model.ViewRole })

索引视图

@model App.WebLib.AspNetCore.WebApp.Areas.AppHelpers.Models.AppHelpersViewModel

@{
    var displayModel = Model.GetDisplayCopy();
}

<form class="form-horizontal">
    <div class="row">
        <div class="col-md-9">
            <h3>Estructura General de las Formas</h3>
            @Html.Partial("_FormLayout")

            <h3>Helper Básicos</h3>

            <h4>Campo de texto</h4>
            @Html.Partial("_TextInput")
            @Html.Partial("_TextInput", displayModel)

            <h4>Campo numérico</h4>
            @Html.Partial("_NumberInput")
            @Html.Partial("_NumberInput", displayModel)
        </div>
    </div>

</form>

模型

public class AppHelpersViewModel : ViewModelBase
{
    public AppHelpersViewModel()
    {
        ViewRole = ViewRole.Edit;
    }

    [Display(Name = "Text Display Name", Prompt = "Text Display Prompt")]
    public string TextInput { get; set; }

    [Display(Name = "Number Display Name")]
    public decimal NumberInput { get; set; }

    [Display(Name = "Date Display Name")]
    public DateTime? DateInput { get; set; }

    [Display(Name = "Bool Display Name", Prompt ="Bool Display Prompt")]
    public bool BoolInput { get; set; }

    [Display(Name = "Required Text Input Label", Prompt = "Placeholder Text")]
    [Required]
    public string RequiredTextInput { get; set; }


    public AppHelpersViewModel GetDisplayCopy()
    {
        var displayCopy = this.MemberwiseClone() as AppHelpersViewModel;

        displayCopy.ViewRole = ViewRole.Display;

        displayCopy.TextInput = "TextInput content";
        displayCopy.RequiredTextInput = "RequiredTextInput content";
        displayCopy.NumberInput = 45.4m;
        displayCopy.DateInput = new DateTime(2016, 10, 24);
        displayCopy.BoolInput = true;

        return displayCopy;
    }
}

视图组件

public class DisplaySourceViewComponent : ViewComponent
{
    public DisplaySourceViewComponent(IHostingEnvironment hostingEnvironment)
    {
        WebRootPath = hostingEnvironment.WebRootPath;
        ContentRootPath = hostingEnvironment.ContentRootPath;
    }

    public string ContentRootPath { get; set; }

    public string WebRootPath { get; set; }

    public async Task<IViewComponentResult> InvokeAsync(string executingViewPath, ViewRole viewRole)
    {
        if (viewRole != ViewRole.Display)
        {
            return new NullViewComponentResult();
        }

        IEnumerable<string> viewSource = await GetViewSourceAsync(executingViewPath);

        return View(viewSource);
    }

    private int GetLastContentIndex(List<string> lines)
    {
        for (int i = lines.Count - 1; i >= 0; i--)
        {
            if (!String.IsNullOrWhiteSpace(lines[i]))
            {
                return i;
            }
        }

        return -1;
    }

    private async Task<IEnumerable<string>> GetViewSourceAsync(string executingViewPath)
    {
        executingViewPath = executingViewPath.Substring(1).Replace('/', Path.DirectorySeparatorChar);

        string viewFilePath = Path.Combine(ContentRootPath, executingViewPath);

        var lines = new List<string>();

        using (var reader = new StreamReader(viewFilePath, Encoding.UTF8))
        {
            string line;

            while ((line = await reader.ReadLineAsync()) != null)
            {
                if (line.StartsWith("@model")) continue;
                if (line.StartsWith("@await") && line.Contains(@"InvokeAsync(""DisplaySource""")) continue;

                lines.Add(line);
            }
        }

        return Trim(lines);
    }

    private IEnumerable<string> Trim(List<string> lines)
    {
        var contentLines = new List<string>();

        int lastContentIndex = GetLastContentIndex(lines);

        for (int i = 0; i <= lastContentIndex; i++)
        {
            string line = lines[i];

            if (String.IsNullOrWhiteSpace(line) && contentLines.Count == 0) continue;

            contentLines.Add(line);
        }

        return contentLines;
    }
}

public class NullViewComponentResult : IViewComponentResult
{
    public void Execute(ViewComponentContext context)
    {
        return;
    }

    public Task ExecuteAsync(ViewComponentContext context)
    {
        return Task.CompletedTask;
    }
}

待办事项

  • 将ViewComponent的用法更改为新的(MVC Core 1.1版本)TagHelpers语法。
  • 以类似的方式包含模型文档。
  • 记录一堆HtmlHelpers [这是我的!;-)]

希望对您有所帮助。


谢谢。我目前不会将我们的MVC5项目(我刚从MVC4升级完成)升级到Core,因此无法使用完全相同的模式,但是您展示的内容很有道理。我会看看是否有一种方法在MVC5中实现类似的方法。 - James Haug
@JamesHaug 我猜你应该能够创建一个辅助函数,而不是使用ViewComponent来完成这个任务。关键部分是使用HtmlHelper中的ViewContext.RouteData,并使用一些约定来查找实际的偏视图文件路径,然后只需读取文件即可。 - Miguel Veloso
其实,现在我写这个的时候意识到我应该能够从HtmlHelper中获取ViewContext,而不必将它作为参数传递。 - Miguel Veloso

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接