Preface: The previous article introduces the encapsulation of ko addition, deletion, modification and search, which indeed saves a lot of js code. The blogger is a person who likes to be lazy. He always feels that these basic additions, deletions, modifications and checks can directly generate page effects through a tool, and no code is needed. That would be so cool. So I studied the grammar of T4. Although I didn’t fully master it, I had a general understanding. So today's article: Quickly generate pages through T4 templates.
KnockoutJS series articles:
BootstrapTable and KnockoutJS combine to achieve the function of adding, deleting, modifying and checking [1]
BootstrapTable and KnockoutJS combine to achieve the function of adding, deleting, modifying and checking [2]
BootstrapTable + KnockoutJS combines to realize the solution of adding, deleting, modifying and checking (3) Two Viewmodels can complete the addition, deleting, modifying and checking
1. Introduction to the use of T4
We know that when adding views in MVC, the page effect of adding, deleting, modifying and checking can be automatically generated. That is because MVC has built-in basic templates for adding, deleting, modifying and checking. The syntax of these templates is to use T4, so where are these templates? After searching for related articles, I found that the location of MVC4 and below version templates is very different from that of MVC5 and above.
•Template location for MVC4 and the following versions: VS installation directory +/ItemTemplates/CSharp/Web/MVC 2/CodeTemplates. For example, the blogger's D:/Program Files (x86)/Microsoft Visual Studio 12.0/Common7/IDE/ItemTemplates/CSharp/Web/MVC 4/CodeTemplates.
Find the template corresponding to cshtml, and there are corresponding tt files that add, delete, modify and check
•MVC5 and above template location: directly give the template location of the blogger D:/Program Files (x86)/Microsoft Visual Studio 12.0/Common7/IDE/Extensions/Microsoft/Web/Mvc/Scaffolding/Templates
Once you know this, the next step is to remodel the template and add your own generated content. You can directly copy the List and Edit templates to the self-transformation, but after thinking about it, it is better not to touch the built-in MVC. It is not better to build your own templates yourself.
Create a new folder under the root directory of the current web project, name it CodeTemplates, and then copy the two template folders MvcControllerEmpty and MvcView in the MVC template to the CodeTemplates folder, remove the original templates inside it, and then create several new templates, as shown in the figure below:
In this way, when we add a new controller and create a new view, we can see our customized template:
2. T4 code introduction
The above introduces how to create your own template. After the template is built, you should start stuffing the corresponding content into it. If the grammar of T4 is expanded, that article will be endless. Interested gardeners can search in the garden. There are still quite a lot of articles. Let’s take a look at a few template contents here. Another thing to note is that it seems that after MVC5, the template file suffix of T4 has been changed to t4, and the previous templates have always ended with tt. Without looking at the differences in their syntax, it is estimated that there should be little difference.
1. Controller.cs.t4
Why rewrite this empty controller template? The blogger thinks that many methods of adding, deleting, modifying and checking need to be manually written and it is a lot of trouble to write a template directly. Let’s take a look at the implementation code in the template:
<#@ template language="C#" HostSpecific="True" #><#@ output extension="cs" #><#@ parameter type="System.String" name="ControllerName" #><#@ parameter type="System.String" name="ControllerRootName" #><#@ parameter type="System.String" name="Namespace" #><#@ parameter type="System.String" name="AreaName" #><#var index = ControllerName.LastIndexOf("Controller");var ModelName = ControllerName.Substring(0, index);#>using System;using System.Collections.Generic;using System.Linq;using System.Web;using System.Web.Mvc;using TestKO.Models;namespace <#= Namespace #>{public class <#= ControllerName #> : Controller{public ActionResult Index(){return View();}public ActionResult Edit(<#= ModelName #> model){return View(model);}[HttpGet]public JsonResult Get(int limit, int offset){return Json(new { }, JsonRequestBehavior.AllowGet);}//Add entity[HttpPost]public JsonResult Add(<#= ModelName #> oData){<#= ModelName #>Model.Add(oData);return Json(new { }, JsonRequestBehavior.AllowGet);}//Update entity [HttpPost]public JsonResult Update(<#= ModelName #> oData){<#= ModelName #>Model.Update(oData);return Json(new { }, JsonRequestBehavior.AllowGet);}//Delete entity [HttpPost]public JsonResult Delete(List<<#= ModelName #> oData){<#= ModelName #>Model.Delete(oData);return Json(new { }, JsonRequestBehavior.AllowGet);}}}This content is not difficult to understand. Just check the generated controller code:
using System;using System.Collections.Generic;using System.Linq;using System.Web;using System.Web.Mvc;using TestKO.Models;namespace TestKO.Controllers{public class UserController: Controller{public ActionResult Index(){return View();}public ActionResult Edit(User model){return View(model);}[HttpGet]public JsonResult Get(int limit, int offset){return Json(new { }, JsonRequestBehavior.AllowGet);}//Add entity [HttpPost]public JsonResult Add(User oData){UserModel.Add(oData); return Json(new { }, JsonRequestBehavior.AllowGet);}//Update entity [HttpPost]public JsonResult Update(User oData){UserModel.Update(oData); return Json(new { }, JsonRequestBehavior.AllowGet);}//Delete entity[HttpPost]public JsonResult Delete(List<User> oData){UserModel.Delete(oData); return Json(new { }, JsonRequestBehavior.AllowGet);}}}2. KoIndex.cs.t4
This template is mainly used to generate list pages, with the general code as follows:
<#@ template language="C#" HostSpecific="True" #><#@ output extension=".cshtml" #><#@ include file="Imports.include.t4" #><#// The following chained if-statement outputs the file header code and markup for a partial view, a view using a layout page, or a regular view.if(IsPartialView) {#><#} else if(IsLayoutPageSelected) {#>@{ViewBag.Title = "<#= ViewName#>";<#if (!String.IsNullOrEmpty(LayoutPageFile)) {#>Layout = "<#= LayoutPageFile#>";<#}#>}<#} else {#>@{Layout = null;}<!DOCTYPE html><html><head><meta name="viewport" content="width=device-width" /><title><#= ViewName #></title><link href="~/Content/bootstrap/css/bootstrap.min.css" rel="stylesheet" /><link href="~/Content/bootstrap-table/bootstrap-table.min.css" rel="stylesheet" /><script src="~/scripts/jquery-1.9.1.min.js"></script><script src="~/Content/bootstrap/js/bootstrap.min.js"></script><script src="~/Content/bootstrap-table/bootstrap-table.min.js"></script><script src="~/Content/bootstrap-table/locale/bootstrap-table-zh-CN.js"></script><script src="~/scripts/knockout/knockout-3.4.0.min.js"></script><script src="~/scripts/knockout/extensions/knockout.mapping-latest.js"></script><script src="~/scripts/extensions/knockout.index.js"></script><script src="~/scripts/extensions/knockout.index.js"></script><script><script src="~/scripts/extensions/knockout.index.js"></script><script src="~/scripts/extensions/knockout.bootstraptable.js"></script><script type="text/javascript">$(function () {var viewModel = {bindId: "div_index",tableParams :{url : "/<#=ViewDataTypeShortName#>/Get",pageSize : 2,},urls :{del : "/<#=ViewDataTypeShortName#>/Delete",edit : "/<#=ViewDataTypeShortName#>/Edit",add : "/<#=ViewDataTypeShortName#>/Edit",},queryCondition :{}};ko.bindingViewModel(viewModel);});</script></head><body><#PushIndent(" ");}#><div id="toolbar"><button data-bind="click:addClick" type="button"><span aria-hidden="true"></span>Added</button><button data-bind="click:editClick" type="button"><span aria-hidden="true"></span>Modify</button><button data-bind="click:deleteClick" type="button"><span aria-hidden="true"></span>Delete</button></div><table data-bind="bootstrapTable:bootstrapTable"><tr><th data-checkbox="true"></th><#IEnumerable<PropertyMetadata> properties = ModelMetadata.Properties;foreach (PropertyMetadata property in properties) {if (property.Scaffold && !property.IsPrimaryKey && !property.IsForeignKey) {#><th data-field="<#= GetValueExpression(property) #>"><#= GetValueExpression(property) #></th><#}}#></tr></thead></table><#// The following code closes the tag used in the case of a view using a layout page and the body and html tags in the case of a regular view page#><#if(!IsPartialView && !IsLayoutPageSelected) {ClearIndent();#></body></html><#}#><#@ include file="ModelMetadataFunctions.cs.include.t4" #>Add a View Index and select this template
The page content obtained
@{Layout = null;}<!DOCTYPE html><html><head><meta name="viewport" content="width=device-width" /><title>Index</title><link href="~/Content/bootstrap/css/bootstrap.min.css" rel="stylesheet" /><link href="~/Content/bootstrap-table/bootstrap-table.min.css" rel="stylesheet" /><script src="~/scripts/jquery-1.9.1.min.js"></script><script src="~/Content/bootstrap/js/bootstrap.min.js"></script><script src="~/Content/bootstrap-table/bootstrap-table.min.js"></script><script src="~/Content/bootstrap-table/locale/bootstrap-table-zh-CN.js"></script><script src="~/scripts/knockout/knockout-3.4.0.min.js"></script><script src="~/scripts/knockout/knockout-3.4.0.min.js"></script><script src="~/scripts/knockout/extensions/knockout.mapping-latest.js"></script><script src="~/scripts/extensions/knockout.index.js"></script><script src="~/scripts/extensions/knockout.bootstraptable.js"></script><script type="text/javascript">$(function () {var viewModel = {bindId: "div_index",tableParams :{url : "/User/Get",pageSize : 2,},urls :{del : "/User/Delete",edit : "/User/Edit",add : "/User/Edit",},queryCondition :{}};ko.bindingViewModel(viewModel);});</script></head><body><div id="toolbar"><button data-bind="click:addClick" type="button"><span aria-hidden="true"></span>Add</button><button data-bind="click:editClick" type="button"><span aria-hidden="true"></span>Modify</button><button data-bind="click:deleteClick" type="button"><span aria-hidden="true"></span>Delete</button></div><table data-bind="bootstrapTable:bootstrapTable"><tr><th data-checkbox="true"></th><th data-field="Name">Name</th><th data-field="FullName">FullName</th><th data-field="Age">Age</th><th data-field="Des">Des</th><th data-field="Createtime">Createtime</th><th data-field="strCreatetime">strCreatetime</th></tr></thead></table></body></html>Index.cshtmlWe moved the viewmodel mentioned in the previous article to the page, so that we don’t have to pass it from the controller every time. Change the column name of the table slightly and the page can run.
Here are a few points to be optimized:
(1) The query conditions have not been generated. If you study the syntax of T4 a little deeper, you can add characteristics to the fields that need to be queried to identify which fields need to be queried, and then automatically generate the corresponding query conditions.
(2) The column names of the table seem to be generated through the field properties of the attribute. This is similar to the first point, and both need to study the grammar of T4.
3. KoEdit.cs.t4
The third template page is the edited template, and its rough code is as follows:
<#@ template language="C#" HostSpecific="True" #><#@ output extension=".cshtml" #><#@ include file="Imports.include.t4" #>@model <#= ViewDataTypeName #><#// "form-control" attribute is only supported for all EditorFor() in System.Web.Mvc 5.1.0.0 or later versions, except for checkbox, which uses a div in Bootstrapstring boolType = "System.Boolean";Version requiredMvcVersion = new Version("5.1.0.0");bool isControlHtmlAttributesSupported = MvcVersion >= requiredMvcVersion;// The following chained if-statement outputs the file header code and markup for a partial view, a view using a layout page, or a regular view.if(IsPartialView) {#><#} else if(IsLayoutPageSelected) {#>@{ViewBag.Title = "<#= ViewName#>";<#if (!String.IsNullOrEmpty(LayoutPageFile)) {#>Layout = "<#= LayoutPageFile#>";<#}#>}<h2><#= ViewName#></h2><#} else {#>@{Layout = null;}<!DOCTYPE html><html><head><meta name="viewport" content="width=device-width" /><title><#= ViewName #></title></head><body><#PushIndent(");}#><#if (ReferenceScriptLibraries) {#><#if (!IsLayoutPageSelected && IsBundleConfigPresent) {#>@Scripts.Render("~/bundles/jquery")@Scripts.Render("~/bundles/jqueryval")<#}#><#else if (!IsLayoutPageSelected) {#><script src="~/Scripts/jquery-<#= JQueryVersion #>.min.js"></script><script src="~/Scripts/jquery.validate.min.js"></script><script src="~/Scripts/jquery.validate.unobtrusive.min.js"></script><#}#><form id="formEdit">@Html.HiddenFor(model => model.Id)<div><#IEnumerable<PropertyMetadata> properties = ModelMetadata.Properties;foreach (PropertyMetadata property in properties) {if (property.Scaffold && !property.IsPrimaryKey && !property.IsForeignKey) {#><div>@Html.LabelFor(model => model.<#= GetValueExpression(property) #>, "<#= GetValueExpression(property) #>", new { @class = "control-label col-xs-2" })<div>@Html.TextBoxFor(model => model.<#= GetValueExpression(property) #>, new { @class = "form-control", data_bind = "value:editModel.<#= GetValueExpression(property) #>" })</div></div><#}}#></div><div><button type="button" data-dismiss="modal"><span aria-hidden="true"></span>Close</button><button type="submit"><span aria-hidden="true"></span>Save</button></div></form><#var index = ViewDataTypeName.LastIndexOf(".");var ModelName = ViewDataTypeName.Substring(index+1, ViewDataTypeName.Length-index-1);#><script src="~/Scripts/extensions/knockout.edit.js"></script><script type="text/javascript">$(function () {var model = @Html.Raw(Newtonsoft.Json.JsonConvert.SerializeObject(Model));var viewModel = {formId: "formEdit",editModel : model,urls :{submit : model.id == 0 ? "/<#= ModelName #>/Add" : "/<#= ModelName #>/Update"},validator:{fields: { Name: {validators: {notEmpty: {message: 'The name cannot be empty!'}}}}}}};ko.bindingEditViewModel(viewModel); });</script><#if(IsLayoutPageSelected && ReferenceScriptLibraries && IsBundleConfigPresent) {#>@section Scripts {@Scripts.Render("~/bundles/jqueryval")}<#}#><#else if(IsLayoutPageSelected && ReferenceScriptLibraries) {#><script src="~/Scripts/jquery-<#= JQueryVersion #>.min.js"></script><script src="~/Scripts/jquery.validate.min.js"></script><script src="~/Scripts/jquery.validate.unobtrusive.min.js"></script><script src="~/Scripts/jquery.validate.unobtrusive.min.js"></script><#}#><#// The following code closes the tag used in the case of a view using a layout page and the body and html tags in the case of a regular view page#><#if(!IsPartialView && !IsLayoutPageSelected) {ClearIndent();#></body></html><#}#><#@ include file="ModelMetadataFunctions.cs.include.t4" #>Generated code:
@model TestKO.Models.User<form id="formEdit">@Html.HiddenFor(model => model.Id)<div><div>@Html.LabelFor(model => model.Name, "Name", new { @class = "control-label col-xs-2" })<div>@Html.TextBoxFor(model => model.Name, new { @class = "form-control", data_bind = "value:editModel.Name" })</div></div><div>@Html.LabelFor(model => model.FullName, "FullName", new { @class = "control-label col-xs-2" })<div>@Html.TextBoxFor(model => model.FullName, new { @class = "form-control", data_bind = "value:editModel.FullName" })</div></div><div>@Html.LabelFor(model => model.Age, "Age", new { @class = "control-label col-xs-2" })<div>@Html.TextBoxFor(model => model.Age, new { @class = "form-control", data_bind = "value:editModel.Age" })</div></div><div>@Html.LabelFor(model => model.Des, "Des", new { @class = "control-label col-xs-2" })<div>@Html.TextBoxFor(model => model.Des, new { @class = "form-control", data_bind = "value:editModel.Des" })</div></div><div>@Html.LabelFor(model => model.Createtime, "Createtime", new { @class = "control-label col-xs-2" })<div>@Html.TextBoxFor(model => model.Createtime, new { @class = "form-control", data_bind = "value:editModel.Createtime" })</div></div><div>@Html.LabelFor(model => model.strCreatetime, "strCreatetime", new { @class = "control-label col-xs-2" })<div>@Html.TextBoxFor(model => model.strCreatetime, new { @class = "form-control", data_bind = "value:editModel.strCreatetime" })</div></div><div><button type="button" data-dismiss="modal"><span aria-hidden="true"></span>Close</button><button type="submit"><span aria-hidden="true"></span>Save</button></div></form><script src="~/Scripts/extensions/knockout.edit.js"></script><script type="text/javascript">$(function () {var model = @Html.Raw(Newtonsoft.Json.JsonConvert.SerializeObject(Model));var viewModel = {formId: "formEdit",editModel : model,urls :{submit : model.id == 0 ? "/User/Add" : "/User/Update"},validator:{fields: { Name: {validators: {notEmpty: {message: 'The name cannot be empty! '}}}}}}};ko.bindingEditViewModel(viewModel); });</script>Edit.cshtmlOf course, the code also needs to be slightly modified. By adding a custom template page, as long as the corresponding entity model in the background is built, you only need to create two new custom views on the front end, and a simple addition, deletion, modification and search can be completed without writing a sentence of js code.
3. Binding of select component
The above introduces the syntax of T4 packaging addition, deletion, modification and search. All components of the page are basically text boxes. However, in actual projects, many query and editing pages will have drop-down boxes to display. How should we deal with the drop-down box? If you don’t keep it a secret, just give a solution. For example, we can place the data source of the drop-down box in the background in the editing page.
User's entity
[DataContract]public class User{[DataMember]public int id { get; set; }[DataMember]public string Name { get; set; }[DataMember]public string FullName { get; set; }[DataMember]public int Age { get; set; }[DataMember]public string Des { get; set; }[DataMember]public DateTime Createtime { get; set; }[DataMember]public string strCreatetime { get; set; }[DataMember]public string DepartmentId { get; set; }[DataMember]public object Departments { get; set; }}Then edit the page
public ActionResult Edit(User model){model.Departments = DepartmentModel.GetData();return View(model);}Then bind the front end.
<div><label for="txt_des">Department</label><select id="sel_dept" data-bind="options: editModel.Departments,optionsText: 'Name',optionsValue: 'Id',value:editModel.DepartmentId"></select></div>
The JS code does not need to be modified. When adding or editing, department fields can be automatically added to the viewmodel.
Of course, the drop-down boxes used by many of our projects are not simply select, because the simple select style is really ugly, so many select components are produced, such as select2, MultiSelect, etc. shared by the blogger before. When using these components to initialize the select, you will find that the drop-down box on the interface is no longer a simple select tag, but is composed of many other tags customized by the component. Let’s take the select2 component as an example to see whether it is feasible to initialize directly according to the above.
We add the last sentence to edit the js code initialized by the page:
<script type="text/javascript">$(function () {var model = @Html.Raw(Newtonsoft.Json.JsonConvert.SerializeObject(Model));var viewModel = {formId: "formEdit",editModel : model,urls :{submit : model.id == 0 ? "/User/Add" : "/User/Update"},validator:{fields: {Name: {validators: {notEmpty: {message: 'The name cannot be empty! '}}}}}}};ko.bindingEditViewModel(viewModel);$("#sel_dept").select2({});});</script>Through the additions and edits, this is indeed feasible! Analysis of the reason, although the page html changes after initializing the select2 component, the component will eventually present the selected value on the original select control. I don’t know if other select initialization components will be like this except select2, and they are waiting to be verified. However, there is one thing to be explained here. Before initializing select2, the options in the drop-down box must be bound to the value, that is, the initialization of the component must be placed after ko.applyBinding().
4. Summary
At this point, ko combined with bootstrapTable template generation and use of select controls are basically available, and of course, it still needs to be improved. If you have time later, the blogger will sort out the combination of other front-end components and ko, such as our most common date control. If you have any questions, please leave me a message and the editor will reply to everyone in time. Thank you very much for your support to Wulin.com website!