ASP。NET Core路到微服务第01部分:构建视图
介绍 说,如果你觉得有点失望找不到任何实际microservices在这篇文章中,这是因为有很多科目我想盖,它不可能谈论他们所有人(或讨论的多)在一篇文章中。另外,我不会马上介绍微服务,因为我想以一种“进化的”逐步的方法工作,在我们前进的道路上重构和推进代码库。所以请耐心点,享受旅程。 由于这只是整个系列的第一篇文章,我想列举下一部分设想的主题: 第1部分:构建视图第2部分:视图组件第3部分:ASP网络核心身份第4部分:sqlite第5部分:短小精悍的第6部分:signalr第7部分:单元测试Web api第8部分:单元测试Web MVC应用程序第9部分:监控健康检查第10部分:复述,数据库部分11:identityserver4 12部分:订购Web api部分13:篮子Web api 14部分:目录Web api 15部分:弹性HTTP客户端与波利16个部分:记录Web api的一部分17:集装箱码头工人18部分:码头工人配置19个部分:中央与Kibana日志 正如我们所看到的,有很多科目要讲。尽管各部分都进行了编号,但这只是为了便于计数。事实上,实际的订单会随着我们的前进而改变。 软件需求 下载并安装Visual Studio Community或superior。 创建项目 项目将使用Visual Studio Community创建(这可以通过Visual Studio代码甚至命令行工具完成),选择MVC项目模板。 MVC代表Model-View-Controller,它是当今普遍存在的软件架构模式,用于在应用关注点分离原则的同时构建用户界面。 模型部分指的是数据承载对象,负责保存在明显的用户界面中显示的信息,以及验证、收集和传输用户类型信息到应用程序后端。 视图部分负责呈现/显示用户界面组件。通常,这些在通俗术语中被称为web页面,但实际上web页面在技术上是一组完整的HTML文件(包括头部、主体和脚注)、图像、图标、CSS样式表、JavaScript代码等等。一个视图可以呈现所有的网页,但是通常每个视图只负责内部页面的内容。 控制器是负责处理向一组视图发出的传入请求、决定需要为视图提供哪些数据、请求和准备这些数据并相应地调用视图的组件。控制器还将处理数据违规,在需要时将应用程序重定向到错误页面。 让我们开始创建一个新的ASP。NET Core MVC项目使用Visual Studio。 首先,从Visual Studio菜单中单击new project,选择Web应用程序选项。 图1:新项目对话框 这将打开向导窗口,我们必须在其中选择Web应用程序(模型-视图-控制器)选项。一定要取消选择“Configure for HTTPS”选项,因为为了简单起见,我们没有使用安全HTTP (HTTPS),至少现在没有。 图2:新ASP。NET Core Web应用程序选择器 一旦项目从选定的MVC模板加载,我们就可以运行(通过按F5键),现在我们可以看到应用程序的主页在我们喜欢的web浏览器中打开。 图3:运行应用程序主页 上面的图像显示了一个非常简单的web应用程序。这个新项目已经为我们提供了基本MVC架构所需的文件。 现在,我们说的是哪些文件?让我们来看看Visual Studio中的解决方案树: 图4:ASP。NET Core MVC项目结构 注意,在上面的图像中,我们为所有MVC部分:模型、视图和控制器设置了项目文件夹。 但是我们的ASP。NET Core MVC应用程序启动?对于每个. net应用程序,可执行文件都有一个入口点,该入口点必须是包含在程序类中的Main()方法。 在一个ASP。NET Core应用程序,Main()方法必须设置并启动一个web主机,即我们的web应用程序的主机。 隐藏,复制Code
public class Program
{
public static void Main(string[] args)
{
CreateWebHostBuilder(args).Build().Run();
} public static IWebHostBuilder CreateWebHostBuilder(string[] args) =>
WebHost.CreateDefaultBuilder(args)
.UseStartup<Startup>();
}
清单1:Program类 正如我们在这里看到的,调用方法webhost . createdefaultbuilder()以创建webhost,但是由于需要配置它,我们还必须调用UseStartup()来传递启动类名,它负责webhost的配置。让我们看看这个类是如何工作的,以及它将如何在我们的应用程序中使用。 这家初创公司结构简单。它只包含两个方法: ConfigureServices()配置() 在这个上下文中,“服务”是任何可以添加来为我们的应用程序提供特定功能的组件,例如:MVC、日志、数据库、身份验证、cookie、会话等等。 这些组件也称为d可以作为“管道”的一部分的“中间体”。每个中间件决定是否将请求传递到管道中的下一个组件,并可能包括在管道中下一个组件之前或之后执行的算法。 通常,一个名为“MyService”的服务会在我们的启动类中被引用两次: 首先,在ConfigureServices()方法中的AddMyService()方法中。在这里,将为AddMyService()方法提供适当的配置,以便服务能够正常运行;然后,在一个UseMyService()中的Configure()方法中。 让我们看看Startup类内部的方法。第一个方法是ConfigureServices(),它首先配置cookie策略选项并将MVC服务添加到应用程序中: 隐藏,复制Code
public class Startup
{
...
// This method gets called by the runtime. Use this method
// to add services to the container.
public void ConfigureServices(IServiceCollection services)
{
services.Configure<CookiePolicyOptions>(options =>
{
// This lambda determines whether user consent
// for non-essential cookies is needed for a given request.
options.CheckConsentNeeded = context => true;
options.MinimumSameSitePolicy = SameSiteMode.None;
});
services.AddMvc().SetCompatibilityVersion(CompatibilityVersion.Version_2_2);
}
...
清单2:启动类中的ConfigureServices()方法 然后配置方法定义一组“Use-Service”方法引用哪个中间件: 隐藏,收缩,复制Code
public class Startup
{
...
// This method gets called by the runtime.
// Use this method to configure the HTTP request pipeline.
public void Configure(IApplicationBuilder app, IHostingEnvironment env)
{
if (env.IsDevelopment())
{
app.UseDeveloperExceptionPage();
}
else
{
app.UseExceptionHandler("/Home/Error");
// The default HSTS value is 30 days.
// You may want to change this for production scenarios,
// see https://aka.ms/aspnetcore-hsts.
app.UseHsts();
} app.UseHttpsRedirection();
app.UseStaticFiles();
app.UseCookiePolicy(); app.UseMvc(routes =>
{
routes.MapRoute(
name: "default",
template: "{controller=Home}/{action=Index}/{id?}");
});
}
}
清单3:启动类中的Configure()方法 这里,我们有一个简短的描述,每个服务将被添加到请求管道: app.UseDeveloperExceptionPage:手术后应用程序的引用已经完成app.useexceptionhandler:添加一个中间件的管道将捕获异常,日志,重置请求路径,并重新执行请求app.usehsts:使静态文件服务为当前请求路径app.usehttpsredirection:添加CookiePolicyMiddleware IApplicationBuilder指定处理程序,使饼干政策功能app.usestaticfiles:启用为当前请求路径服务的静态文件应用程序。usecookiepolicy:将cookiepolicy中间件处理程序添加到指定的IApplicationBuilder中,从而启用cookie策略功能 索引页 我们将在我们的商店里以30种有限的产品进行销售。对于每一个,我们都有一个图像,将被添加到wwwroot项目文件夹中的/images/catalog文件夹中。 图5:产品目录图片 这些产品将在主页内的“目录”视图中显示。这个目录显示为按每个产品类别分组的一组产品。每个类别都有一个称为“Carousel”的Bootstrap 4组件,它自动以4个产品为一组旋转类别产品。 隐藏,收缩,复制Code
@{
ViewData["Title"] = "Home Page";
} @for (int category = 0; category < 6; category++)
{
<h3>Category Name</h3> <divid="carouselExampleIndicators-@category"class="carousel slide"data-ride="carousel">
<olclass="carousel-indicators">
<lidata-target="#carouselExampleIndicators-@category"data-slide-to="0"class="active"></li>
<lidata-target="#carouselExampleIndicators-@category"data-slide-to="1"></li>
<lidata-target="#carouselExampleIndicators-@category"data-slide-to="2"></li>
</ol>
<divclass="carousel-inner">
<divclass="carousel-item active">
<divclass="container">
<divclass="row">
@for (int i = 0; i < 4; i++)
{
<divclass="col-sm-3">
<divclass="card">
<divclass="card-body">
<imgclass="d-block w-100"src="~/images/catalog/large_@((i+1 +
category * 5).ToString("000")).jpg">
</div>
<divclass="card-footer">
<pclass="card-text">Product Name</p>
<h5class="card-title text-center">$ 39.90</h5>
<divclass="text-center">
<ahref="#"class="btn btn-success">
Add to basket
</a>
</div>
</div>
</div>
</div>
}
</div>
</div>
</div>
<divclass="carousel-item">
<divclass="container">
<divclass="row">
@for (int i = 0; i < 1; i++)
{
<divclass="col-sm-3">
<divclass="card">
<divclass="card-body">
<imgclass="d-block w-100"src="~/images/catalog/large_@((i+5 +
category * 5).ToString("000")).jpg">
</div>
<divclass="card-footer">
<pclass="card-text">Product Name</p>
<h5class="card-title text-center">$ 39.90</h5>
<divclass="text-center">
<ahref="#"class="btn btn-success">
Add to basket
</a>
</div>
</div>
</div>
</div>
}
</div>
</div>
</div>
</div>
<aclass="carousel-control-prev"href="#carouselExampleIndicators-@category"role="button"data-slide="prev">
<spanclass="carousel-control-prev-icon"aria-hidden="true"></span>
<spanclass="sr-only">Previous</span>
</a>
<aclass="carousel-control-next"href="#carouselExampleIndicators-@category"role="button"data-slide="next">
<spanclass="carousel-control-next-icon"aria-hidden="true"></span>
<spanclass="sr-only">Next</span>
</a>
</div>
}
清单4:目录索引视图 样式 这是一个小步骤,但由于Bootstrap 4不再提供图标字体(gliphicon),这取决于我们自己安装它。 Visual Studio允许我们安装客户机库,比如字体Awesome,一个流行的图标字体包。 图6:添加客户端库菜单 图7:添加客户端库对话框 现在已经安装了字体文件,我们必须在_Layout中引用它们。cshtml文件: 隐藏,复制Code
<linkhref="~/lib/font-awesome/css/font-awesome.css"rel="stylesheet"/>
<linkrel="stylesheet"href="~/css/site.css"/>
清单5:向_Layout添加字体Awesome引用。cshtml文件 让我们看看如何添加我们的第一个图标。在家里/索引。在cshtml视图中,我们添加了一个HTML元素和fa fa-shopping-cart类。 隐藏,复制Code
<ahref="#"class="btn btn-success">
<spanclass="fa fa-shopping-cart"></span>
Add to basket
</a>
清单6:在catalog的Index视图中使用字体Awesome图标 这将在“添加到购物篮”按钮的左边自动显示购物车图标。 再次运行这个应用程序,我们看到购物车图标是如何呈现的: 图8:字体很棒的图标 品牌 通过打开_Layout。cshtml文件,我们可以用我们公司的名字来更改品牌。 隐藏,复制Code
<divclass="container">
© 2019 - The Grocery Store - <aasp-area=""asp-controller="Home"asp-action="Privacy">Privacy</a>
</div>
清单7:在_Layout.cshtml中更改公司名称 现在,由于默认的ASP。NET Core MVC模板不包括品牌,让我们自己包括它。 我们还必须在顶部栏中包括公司的徽标,首先为导航栏定义一个背景CSS规则: 图9:logo.png文件 隐藏,复制Code
a.navbar-brand {
...
background: url('../images/logo.png');
...
}
清单8:在导航栏背景中定义公司的徽标 局部视图 如果您看一下我们的catalog index Razor文件,就会发现它变得又大又复杂,这可能会影响其内容的可读性和理解。 ASP。NET Core,我们可以使用部分视图来轻松地将大型标记文件分解为更小的组件,比如目录视图。 部分视图是一个Razor文件(.cshtml),它在另一个标记文件呈现的输出中呈现HTML元素。 不再是一个单一的视图文件,现在我们的目录视图将由各种逻辑部分组成: 视图/目录 索引。cshtml _searchproducts。cshtml _categories。cshtml _productcard.cshtml 通过将独立的部分作为部分视图来处理,每个文件现在都比全合一视图文件具有更多的可维护性。 要将部分视图应用于我们的应用程序,首先我们将大部分标记内容提取到一个新的_Categories。cshtml文件。注意,_Categories。cshtml以下划线开始,这是部分vi的默认命名约定评述。 原来的指数。cshtml文件必须包含_Categories的元素。cshtml标记。标签实际上是一个标签帮助器。PartialTagHelper类),它运行在服务器上并在该位置呈现类别。 隐藏,复制Code
@{
ViewData["Title"] = "Catalog";
var products = Enumerable.Range(0, 30);
} <partialname="_Categories"for="@products"/>
清单9:目录/索引。cshtml文件 除了PartialTagHelper之外,还可以使用HtmlHelper引用部分视图。使用HtmlHelper的最佳实践是调用PartialAsync。在下面的代码片段中,PartialAsync方法返回封装在task中的IHtmlContent类型。该方法是通过在等待的调用前加上@字符来引用的,以表示Razor引擎。 隐藏,复制Code
@{
ViewData["Title"] = "Catalog";
var products = Enumerable.Range(0, 30);
} @await Html.PartialAsync("_Categories", products);
清单9.1:Html。目录/索引中的部分异步变体。cshtml文件 注意,清单9.1中的代码产生的结果与清单9中的代码完全相同。还要注意我们如何将products模型作为方法的参数传递。 另一方面,_Categories。cshtml文件看起来与任何普通Razor标记文件完全相同:我们可以定义@model指令、HTML元素、标记助手、c#代码等等。你也可以使用标签助手包括内部部分视图,就像下面的文件: 隐藏,收缩,复制Code
@model IEnumerable<int>; @{
var products = Model;
const int productsPerCategory = 5;
const int PageSize = 4;
} @for (int category = 0; category < (products.Count() / productsPerCategory); category++)
{
<h3>Category @(category + 1)</h3> <divid="carouselExampleIndicators-@category"class="carousel slide"data-ride="carousel">
<divclass="carousel-inner">
@{
int pageCount = (int)Math.Ceiling((double)productsPerCategory / PageSize);
var productsInCategory =
products
.Skip(category * productsPerCategory)
.Take(productsPerCategory); for (int pageIndex = 0; pageIndex < pageCount; pageIndex++)
{
<div class="carousel-item @(pageIndex == 0 ? "active" : "")">
<div class="container">
<div class="row">
@{
var productsInPage =
productsInCategory
.Skip(pageIndex * PageSize)
.Take(PageSize); foreach (var productIndex in productsInPage)
{
<partial name="_ProductCard" for="@productIndex"/>
}
}
</div>
</div>
</div>
}
}
</div>
<aclass="carousel-control-prev"href="#carouselExampleIndicators-@category"role="button"data-slide="prev">
<spanclass="carousel-control-prev-icon"aria-hidden="true"></span>
<spanclass="sr-only">Previous</span>
</a>
<aclass="carousel-control-next"href="#carouselExampleIndicators-@category"role="button"data-slide="next">
<spanclass="carousel-control-next-icon"aria-hidden="true"></span>
<spanclass="sr-only">Next</span>
</a>
</div>
}
清单10:目录/ _Categories。cshtml文件 现在,最后一个目录部分视图必须是包含产品卡片详细信息的视图。 隐藏,复制Code
@model int; @{
var productIndex = Model;
} <divclass="col-sm-3">
<divclass="card">
<divclass="card-body">
<imgclass="d-block w-100"src="~/images/catalog/large_@((productIndex + 1).ToString("000")).jpg">
</div>
<divclass="card-footer">
<pclass="card-text">Product Name</p>
<h5class="card-title text-center">$ 39.90</h5>
<ahref="#"class="btn btn-success">
<spanclass="fa fa-shopping-cart"></span>
Add to basket
</a>
</div>
</div>
</div>
</div>
清单11:目录/ _ProductCard。cshtml文件 注意产品图像URL是如何通过适当的路径提供的,方法是将产品代码与图像路径的其余部分连接起来。 搜索产品部分视图 目录索引视图不仅用于显示,而且用于搜索产品。上面的部分将提供一个表单,用户将在其中输入并提交搜索文本,因此只有匹配的产品或类别名称将显示在目录中。 同样,我们应该在主索引中添加一个新的部分视图标记助手(<partial>)。cshtml剃刀文件。 隐藏,复制Code
@ {
var products = Enumerable.Range(0, 30);
} <partialname="_SearchProducts"/> <partialname="_Categories"for="@products"/>
清单12:目录/ _ProductCard。cshtml文件 注意索引视图是如何保持整洁和简单的。而且由于_SearchProducts部分视图不需要任何数据,所以没有参数传递给它。 _SearchProducts部分视图基本上是一个表单,其中包含向服务器发送信息所需的一些元素(标签+文本字段+提交按钮)。 隐藏,收缩,复制Code
<divclass="container">
<h2>Search products</h2>
<divid="custom-search-input">
<divclass="input-group col-md-12">
<form>
<divclass="container">
<divclass="row">
<div>
<inputtype="text"name="search"class="form-control input-lg"placeholder="category or product"/>
</div>
<div>
<divclass="input-group-btn text-center">
<ahref="#"class="btn btn-success">
<spanclass="fa fa-search"></span>
</a>
</div>
</div>
</div>
</div>
</form>
</div>
</div>
</div>
清单13:目录/ _SearchProducts。cshtml文件 到目前为止,表单没有做任何事情。但是我们将在下一篇文章中实现搜索功能。 篮子里的观点 用户选择任何产品后,必须将其重定向到“我的购物篮”视图。此视图负责购物车功能,并将保存订单项信息的列表,例如: 产品形象产品名称项目数量单价小计 到目前为止,我们只有HomeController,也就是我们的catalog Index()操作所在的位置。我们也可以使用HomeController来保存篮子索引,但是为了不使应用程序中唯一的控制器混乱,让我们保留一个用于目录的控制器和另一个用于篮子的控制器。 但是由于“HomeController”不能说明很多内容,让我们通过将其名称更改为“CatalogController”来使其更具描述性。这也需要我们将视图/主文件夹重命名为视图/目录: 图10:将主控制器重命名为Catalog 由于CatalogController也持有一个显示错误视图的通用操作,最好将该操作提取到一个超类,这是一个由CatalogController和BasketController继承的基类: 隐藏,复制Code
public abstract class BaseController : Controller
{
[ResponseCache(Duration = 0, Location = ResponseCacheLocation.None, NoStore = true)]
public IActionResult Error()
{
return View(new ErrorViewModel
{ RequestId = Activity.Current?.Id ?? HttpContext.TraceIdentifier });
}
}
清单14:BaseController.cs文件 现在让我们让两个控制器从基类继承: 隐藏,复制Code
public class CatalogController : BaseController
{
public IActionResult Index()
{
return View();
}
} public class BasketController : BaseController
{
public IActionResult Index()
{
return View();
}
}
清单15:CatalogController和BasketController类 此时,如果您试图再次执行应用程序,您会注意到应用程序是如何崩溃的,因为它仍然在寻找位于名为HomeController的控制器上的索引操作。这被称为“默认路由”,它是在我们使用MVC项目模板创建新项目时配置的。 现在,我们必须改变默认的路由,将默认控制器从“Home”重命名为“Catalog”: 隐藏,复制Code
app.UseMvc(routes =>
{
routes.MapRoute(
name: "default",
template: "{controller=Catalog}/{action=Index}/{id?}");
});
清单16:在Startup.cs中设置到CatalogController的新默认路由 至于篮子视图,我们再次使用引导组件来创建用户界面。它基本上是一个引导卡片组件,包括一个卡片头,其中包含用于篮子项目名称的多列标题,用于篮子项目详细信息的卡片主体,以及用于总数/项目计数的卡片脚。 图11:引导4卡组件 正如我们所看到的,到目前为止,购物篮项目数据只是在视图本身中声明的数组。稍后,该数据将被来自控制器的数据替换。 隐藏,收缩,复制Code
@{
ViewData["Title"] = "My Basket"; var items = new[]
{
new { Id = 1, ProductId = 1, Name = "Broccoli", UnitPrice = 59.90, Quantity = 2 },
new { Id = 2, ProductId = 5, Name = "Green Grapes", UnitPrice = 59.90, Quantity = 3 },
new { Id = 3, ProductId = 9, Name = "Tomato", UnitPrice = 59.90, Quantity = 4 }
};
} <divclass="row">
<divclass="col-sm-12">
<divclass="pull-right">
<aclass="btn btn-success"href="/">
Add More Products
</a>
<aclass="btn btn-success"href="/registration">
Fill in Registration
</a>
</div>
</div>
</div> <h3>My Basket</h3> <divclass="card">
<divclass="card-header">
<divclass="row">
<divclass="col-sm-6">
Item
</div>
<divclass="col-sm-2 text-center">
Unit Price
</div>
<divclass="col-sm-2 text-center">
Quantity
</div>
<divclass="col-sm-2">
<spanclass="pull-right">
Subtotal
</span>
</div>
</div>
</div>
<divclass="card-body">
@foreach (var item in items)
{
<divclass="row row-center">
<divclass="col-sm-2">
<imgclass="img-product-basket w-75"src="/images/catalog/large_@(item.ProductId.ToString("000")).jpg"/>
</div>
<inputtype="hidden"name="productId"value="012"/>
<divclass="col-sm-4">@item.Name</div>
<divclass="col-sm-2 text-center">@item.UnitPrice.ToString("C")</div>
<divclass="col-sm-2 text-center">
<divclass="input-group">
<buttontype="button"class="btn btn-light">
<spanclass="fa fa-minus"></span>
</button>
<inputtype="text"value="@item.Quantity"class="form-control text-center quantity"/>
<buttontype="button"class="btn btn-light">
<spanclass="fa fa-plus"></span>
</button>
</div>
</div>
<divclass="col-sm-2">
<divclass="pull-right">
<spanclass="pull-right"subtotal>
@((item.Quantity * item.UnitPrice).ToString("C"))
</span>
</div>
</div>
</div>
<br/>
}
</div>
<divclass="card-footer">
<divclass="row">
<divclass="col-sm-10">
<spannumero-items>
Total: @items.Length
item@(items.Length > 1 ? "s" : "")
</span>
</div>
<divclass="col-sm-2">
Total: <spanclass="pull-right"total>
@(items.Sum(item => item.Quantity * item.UnitPrice).ToString("C"))
</span>
</div>
</div>
</div>
</div> <br/> <divclass="row">
<divclass="col-sm-12">
<divclass="pull-right">
<aclass="btn btn-success"href="/">
Add More Products
</a>
<aclass="btn btn-success"href="/registration">
Fill in Registration
</a>
</div>
</div>
</div>
清单17:目录/索引。cshtml文件 最后一点,我们现在可以通过添加一个CSS规则来对齐篮子中的项目: 隐藏,复制Code
.row-center {
display: flex;
align-items: center;
}
清单18:站点.css文件 注意我们是如何使用flexbox布局的,与Bootstrap 4中使用的布局完全相同。 图12:篮子列表 篮子局部视图 再一次,我们将大篮子视图分解为部分视图,就像我们对目录标记所做的那样。 在处理部分视图之前,让我们创建一个新的类来保存购物篮项数据: 隐藏,复制Code
public class BasketItem
{
public int Id { get; set; }
public int ProductId { get; set; }
public string Name { get; set; }
public decimal UnitPrice { get; set; }
public int Quantity { get; set; }
}
清单19:在BasketController.cs中定义的模拟篮子项目类 部分视图的优点之一是可重用性。我们的购物篮项目有两个部分,一个在购物篮列表卡的上面,另一个在下面,它们都有完全相同的控制按钮: 添加更多的产品填写注册 隐藏,复制Code
<divclass="row">
<divclass="col-sm-12">
<divclass="pull-right">
<aclass="btn btn-success"href="/">
Add More Products
</a>
<aclass="btn btn-success"href="/">
Fill in Registration
</a>
</div>
</div>
</div>
清单20:/ _BasketControls篮子。cshtml文件 正如我们所看到的,这些标记是重复的。幸运的是,部分视图允许我们避免这种重复。 主篮子视图现在看起来简单多了,在篮子列表部分视图的上面和下面都实现了_BasketControls部分视图。 隐藏,复制Code
@using MVC.Controllers
@{
ViewData["Title"] = "My Basket";
List<BasketItem> items = new List<BasketItem>
{
new BasketItem { Id = 1, ProductId = 1, Name = "Broccoli",
UnitPrice = 59.90m, Quantity = 2 },
new BasketItem { Id = 2, ProductId = 5, Name = "Green Grapes",
UnitPrice = 59.90m, Quantity = 3 },
new BasketItem { Id = 3, ProductId = 9, Name = "Tomato",
UnitPrice = 59.90m, Quantity = 4 }
};
} <partialname="_BasketControls"/> <h3>My Basket</h3> <partialname="_BasketList"for="@items"/>
<br/>
<partialname="_BasketControls"/>
清单21:篮子/索引。cshtml文件 下面是提取到一个新的部分视图(_BasketList.cshtml)的篮子列表标记: 隐藏,收缩,复制Code
@using MVC.Controllers
@model List<BasketItem>; @{
var items = Model;
} <divclass="card">
<divclass="card-header">
<divclass="row">
<divclass="col-sm-6">
Item
</div>
<divclass="col-sm-2 text-center">
Unit Price
</div>
<divclass="col-sm-2 text-center">
Quantity
</div>
<divclass="col-sm-2">
<spanclass="pull-right">
Subtotal
</span>
</div>
</div>
</div>
<divclass="card-body">
@foreach (var item in items)
{
<partialname="_BasketItem"for="@item"/>
}
</div>
<divclass="card-footer">
<divclass="row">
<divclass="col-sm-10">
<spannumero-items>
Total: @items.Count
item@(items.Count > 1 ? "s" : "")
</span>
</div>
<divclass="col-sm-2">
Total: <spanclass="pull-right"total>
@(items.Sum(item => item.Quantity* item.UnitPrice).ToString("C"))
</span>
</div>
</div>
</div>
</div>
清单22:/ _BasketList篮子。cshtml文件 对于购物篮项的详细信息,我们然后创建最后一个部分视图,作为_BasketItem。cshtml文件。注意小计是如何计算的,用数量乘以单价: 隐藏,收缩,复制Code
@using MVC.Controllers @model BasketItem @{
var item = Model;
} <divclass="row row-center product-line"item-id="@item.Id.ToString("000")">
<divclass="col-sm-2">
<imgclass="img-product-basket w-75"src="/images/catalog/large_@(item.ProductId.ToString("000")).jpg"/>
</div>
<inputtype="hidden"name="productId"value="012"/>
<divclass="col-sm-4">@item.Name</div>
<divclass="col-sm-2 text-center">@item.UnitPrice.ToString("C")</div>
<divclass="col-sm-2 text-center">
<divclass="input-group">
<buttontype="button"class="btn btn-light">
<spanclass="fa fa-minus"></span>
</button>
<inputtype="text"value="@item.Quantity"class="form-control text-center quantity"/>
<buttontype="button"class="btn btn-light">
<spanclass="fa fa-plus"></span>
</button>
</div>
</div>
<divclass="col-sm-2">
<divclass="pull-right">
<spanclass="pull-right"subtotal>
@((item.Quantity * item.UnitPrice).ToString("C"))
</span>
</div>
</div>
</div>
<br/>
清单23:/ _BasketItem篮子。cshtml文件 注册视图 在用户决定在购物车中包含哪些产品和数量后,用户可以选择继续完成订单。但是首先,需要一些个人信息,这通常是典型的电子商务流程所需要的,比如账单、发票和运输等等。 图13:注册视图 隐藏,复制Code
using Microsoft.AspNetCore.Mvc; namespace MVC.Controllers
{
public class RegistrationController : BaseController
{
public IActionResult Index()
{
return View();
}
}
}
清单24:RegistrationController类 接下来,注册视图必须包含收集个人信息所需的所有字段: 隐藏,收缩,复制Code
<h3>Registration</h3> <formmethod="post"action="/">
<divclass="card">
<divclass="card-body">
<divclass="row">
<divclass="col-sm-4">
<divclass="form-group">
<labelclass="control-label">Customer Name</label>
<inputtype="text"class="form-control"/>
</div>
<divclass="form-group">
<labelclass="control-label">Email</label>
<inputtype="email"class="form-control"/>
</div>
<divclass="form-group">
<labelclass="control-label">Phone</label>
<inputtype="text"class="form-control"/>
</div>
</div>
<divclass="col-sm-4">
<divclass="form-group">
<labelclass="control-label">Address</label>
<inputtype="text"class="form-control"/>
</div>
<divclass="form-group">
<labelclass="control-label">Additional Address</label>
<inputtype="text"class="form-control"/>
</div>
<divclass="form-group">
<labelclass="control-label">District</label>
<inputtype="text"class="form-control"/>
</div>
</div>
<divclass="col-sm-4">
<divclass="form-group">
<labelclass="control-label">City</label>
<inputtype="text"class="form-control"/>
</div>
<divclass="form-group">
<labelclass="control-label">State</label>
<inputtype="text"class="form-control"/>
</div>
<divclass="form-group">
<labelclass="control-label">Zip Code</label>
<inputtype="text"class="form-control"/>
</div> <divclass="form-group">
<aclass="btn btn-success"href="/">
Keep buying
</a>
</div>
<divclass="form-group">
<buttontype="submit"class="btn btn-success button-notification">
Check out
</button>
</div>
</div>
</div>
</div>
</div>
</form>
清单25:注册/索引。cshtml文件 请注意,我们再次省略了表单操作,因为数据库更新功能将在将来提供。 结帐视图 一旦客户填写个人信息,假设一切都好关于这个过程,然后将他/她重定向到一个新的web页面通知客户订单已放置,要求他/她等待进一步指示尽快处理,生成订单。 现在,Checkout控制器还是一个非常简单的类,就像其他类一样: 隐藏,复制Code
public class CheckoutController : BaseController
{
public IActionResult Index()
{
return View();
}
}
清单26:CheckoutController类 该视图只有几行标记,带有带有post-basket指令的静态内容。这里唯一的动态信息是客户电子邮件地址。 隐藏,复制Code
@{
ViewData["Title"] = "Checkout";
var email = "alice@smith.com";
}
<h3>Order Has Been Placed!</h3> <divclass="panel-info">
<p>Your order has been placed.</p>
<p>Soon you will receive an e-mail at <b>@email</b> including all order details.</p>
<p><ahref="/"class="btn btn-success">Back to product catalog</a></p>
</div>
清单27:签出/索引。cshtml文件 图14:Checkout视图 我们的应用程序流要求订单不是在购物车的签出时立即处理,而是在将来的某个时候异步处理。 通知视图 隐藏,复制Code
public class NotificationsController : BaseController
{
public IActionResult Index()
{
return View();
}
}
清单28:NotificationsController类 随着客户继续购买,异步订购流程可能需要一些时间来持久化实际的数据库订单数据细节。因此,我们有一个通知视图,在这个视图中,客户可以检查他/她以前的购买,并从这里获得关于实际订单的更多信息,例如发票、运输等等。 隐藏,收缩,复制Code
@{
ViewData["Title"] = "Notifications";
}
<h3>User Notifications</h3> <divclass="row">
<divclass="col-sm-12">
<divclass="pull-right">
<aclass="btn btn-success"href="/">
Back to Catalog
</a>
</div>
</div>
</div>
<br/> <divclass="card">
<divclass="card-header">
<divclass="row">
<divclass="col-sm-2 text-center">
<!--NEW?-->
</div>
<divclass="col-sm-8">
Message
</div>
<divclass="col-sm-2 text-center">
Date / Time
</div>
</div>
</div>
<divclass="card-body notifications">
<divclass="row">
<divclass="col-sm-2 text-center">
<spanclass="fa fa-envelope-open"></span>
</div>
<divclass="col-sm-8">
New order placed successfully: 2
</div>
<divclass="col-sm-2 text-center">
<span>
13/04/2019
</span>
<span>
18:04
</span>
</div>
</div>
</div>
</div>
<br/>
<divclass="row">
<divclass="col-sm-12">
<divclass="pull-right">
<aclass="btn btn-success"href="/">
Back to Catalog
</a>
</div>
</div>
</div>
清单29:通知/索引。cshtml文件 图15:通知视图 JSON产品负载 到目前为止,我们的目录并没有显示实际的产品,而是显示了模拟数据。让我们开始一个新的重构周期,这样我们就可以将更多真实的数据注入到目录视图中。 这类数据通常来自数据库或web服务。但是在我们的例子中,让我们通过读取静态JSON文件来检索它们。这些产品。json文件放在我们的项目文件夹的根目录下,它的内容是这样的: 隐藏,复制Code
[
{
"number": 1,
"name": "Oranges",
"category": "Fruits",
"price": 5.90
},
{
"number": 2,
"name": "Lemons",
"category": "Fruits",
"price": 5.90
},
.
.
.
]
清单30:产品。json文件 在现实场景中,我们的目录数据库最初将使用这个JSON文件数据填充。这个过程叫做“播种”。我们将用JSON文件“播种”数据库。但是由于我们还没有数据库,所以我们将使用种子数据作为目录视图的直接源。 我们还没有对“MVC”中的“M”部分做太多的工作。对于这个模型,我们创建了两个类:Product和Category。因为两个类都会我有Id属性,我们可以将它移动到超类中,由模型类继承。 隐藏,复制Code
using System.Runtime.Serialization; namespace MVC.Models
{
public abstract class BaseModel
{
public int Id { get; set; }
}
}
清单31:BaseModel类 隐藏,复制Code
public class Category : BaseModel
{
public Category(int id, string name)
{
Id = id;
Name = name;
} public string Name { get; private set; }
}
清单32:Category类 对于Product类,我们可以提供一个新的只读ImageURL属性,用于计算图像路径。这将免去视图构建路径的责任。 隐藏,复制Code
public class Product : BaseModel
{
public Category Category { get; set; }
public string Code { get; set; }
public string Name { get; set; }
public decimal Price { get; set; }
public string ImageURL { get { return $"/images/catalog/large_{Code}.jpg"; } } public Product(int id, string code, string name, decimal price, Category category)
{
Id = id;
Code = code;
Name = name;
Price = price;
Category = category;
}
}
清单33:Product类 以下班级负责阅读产品。json文件,将其反序列化为产品对象集合,然后返回产品列表。 隐藏,收缩,复制Code
public class SeedData
{
public static async Task<List<Product>> GetProducts()
{
var json = await File.ReadAllTextAsync("products.json");
var data = JsonConvert.DeserializeObject<List<ProductData>>(json); var dict = new Dictionary<string, Category>(); var categories =
data
.Select(i => i.category)
.Distinct(); foreach (var name in categories)
{
var category = new Category(dict.Count + 1, name);
dict.Add(name, category);
} var products = new List<Product>(); foreach (var item in data)
{
Product product = new Product(
products.Count + 1,
item.number.ToString("000"),
item.name,
item.price,
dict[item.category]);
products.Add(product);
} return products;
}
} public class ProductData
{
public int number { get; set; }
public string name { get; set; }
public string category { get; set; }
public decimal price { get; set; }
}
清单34:SeedData类 当然,我们也有一些代码需要重构。第一个要修改的组件是目录控制器。 我们将把产品列表加载到一个局部变量中,然后将其作为模型参数传递到视图中。 隐藏,复制Code
public class CatalogController : BaseController
{
public async Task<IActionResult> Index()
{
var products = await SeedData.GetProducts();
return View(products);
}
}
清单35:CatalogController类 另外,模型类型必须修改为在catalog Index视图中列出。 隐藏,复制Code
@model List<Product>;
@using MVC.Models;
@{
ViewData["Title"] = "Catalog";
} <partialname="_SearchProducts"/> <partialname="_Categories"for="@Model"/>
清单36:Index.cshtml 现在我们必须用c#表达式替换product字段,从模型中获取数据: @ @product (product.ImageURL)。名字@product.price.tostring(“C”) 隐藏,复制Code
@model Product;
@using MVC.Models; @{
var product = Model;
} <divclass="col-sm-3">
<divclass="card">
<divclass="card-body">
<imgclass="d-block w-100"src="@(product.ImageURL)">
</div>
<divclass="card-footer">
<pclass="card-text">@product.Name</p>
<h5class="card-title text-center">@product.Price.ToString("C")</h5>
<divclass="text-center">
<ahref="#"class="btn btn-success">
<spanclass="fa fa-shopping-cart"></span>
.
.
.
清单37:Catalog/_ProductCard。cshtml文件 另外,_Categories部分视图将被重构。首先,我们将模型类型更改为List,并将categories变量赋值更改为LINQ查询,该查询只提供产品列表中的不同类别对象。 隐藏,收缩,复制Code
@model List<Product>; @{
var products = Model;
const int PageSize = 4;
var categories = products.Select(p => p.Category).Distinct();
}
.
.
.
@foreach (var category in categories)
{
<h3>@category.Name</h3> <divid="carouselExampleIndicators-@category.Id"class="carousel slide"data-ride="carousel">
.
.
.
var productsInCategory = products
.Where(p => p.Category.Id == category.Id);
int pageCount = (int)Math.Ceiling((double)productsInCategory.Count() / PageSize);
.
.
.
<aclass="carousel-control-prev"href="#carouselExampleIndicators-@category.Id"role="button"data-slide="prev">
.
.
.
<aclass="carousel-control-next"href="#carouselExampleIndicators-@category.Id"role="button"data-slide="next">
清单38:Catalog/_Categories。cshtml文件 因为我们使用的是不同的Bootstrap 4 Carousel组件,所以它们必须由类别id属性(@category.Id)标识。 productsInCategory局部变量现在保存每个类别中的产品集合,我们将这些产品分组,以便可以适当地填充每个carousel。 应用程序导航 到目前为止,每个视图仍然是隔离的,并且没有链接将视图彼此连接起来。让我们使用AnchorTagHelper来提供导航,以生成正确的链接。 虽然有相同的HTML标签的外观,AnchorTagHelper实际上运行在服务器端,在那里它计算锚URL的属性,如: MVC控制器名称。当省略时,将假定当前控制器。asp-action:路径名。省略时,将采用默认操作(Index)。asp-route-*:动作参数。每个动作参数必须单独提供。 第一个链接将从目录视图到篮子列表。每次客户选择一种产品时,购物车必须显示,显示所选的产品和一个数量。 我们如何改变一个普通的HTML锚元素到一个锚标签助手? 首先,我们取当前锚元素… 隐藏,复制Code
<ahref="#"class="btn btn-success">
清单39:标记助手之前的锚元素 添加一个新的asp-controller属性: 隐藏,复制Code
<aasp-controller="basket"class="btn btn-success">
清单40:标记帮助器后的锚元素 源代码中的这个小变化会产生很大的影响:当ASP。NET Core使用Razor SDK来编译视图,这将注意到asp-controller属性,所以新的链接将不再作为HTML锚元素处理。相反,就像任何其他标签助手,它现在是一个服务器端组件,运行在服务器上并呈现实际的HTML链接: 隐藏,复制Code
<aclass="btn btn-success"href="/Basket">
清单41:服务器端由标记助手呈现的锚元素 现在让我们在篮子控件部分视图中也应用AnchorTagHelper: 隐藏,复制Code
.
.
.
<divclass="pull-right">
<aasp-controller="catalog"class="btn btn-success">
Add More Products
</a>
<aasp-controller="registration"class="btn btn-success">
Fill in Registration
</a>
</div>
清单42:/ _BasketControls篮子。cshtml文件 结论 本系列文章的第一部分到此结束。如果您到达了这条线,非常感谢您的耐心等待。如果你喜欢这篇文章,或者有任何抱怨或建议,请在下面留下评论。我会很高兴得到你的反馈! 我们已经了解了如何创建一个新的ASP。NET Core项目,使用Razor引擎开发基本视图,为视图提供基本模型,并使用锚标记助手将它们链接在一起。我们将使用相同的项目作为下一篇文章的起点,在这篇文章中我们将处理视图组件。 历史 2019-04-20:初始版本 本文转载于:http://www.diyabc.com/frontweb/news19475.html
ASP。NET Core路到微服务第01部分:构建视图的更多相关文章
- Ubuntu & Docker & Consul & Fabio & ASP.NET Core 2.0 微服务跨平台实践
相关博文: Ubuntu 简单安装 Docker Mac OS.Ubuntu 安装及使用 Consul Consul 服务注册与服务发现 Fabio 安装和简单使用 阅读目录: Docker 运行 C ...
- Docker & Consul & Fabio & ASP.NET Core 2.0 微服务跨平台实践
相关博文: Ubuntu 简单安装 Docker Mac OS.Ubuntu 安装及使用 Consul Consul 服务注册与服务发现 Fabio 安装和简单使用 阅读目录: Docker 运行 C ...
- ASP.NET Core基于微软微服务eShopOnContainer事件总线EventBus的实现
这个EventBus的实现是基于微软微服务https://github.com/dotnet-architecture/eShopOnContainers项目的,我把它从项目中抽离出来,打包成nuge ...
- asp.net core 搭建WebAPI微服务-----cosnul服务
参考网址:https://blog.csdn.net/weixin_42084199/article/details/108643555 在此之前需要准备的是: vs2019,以往版本不支持dotne ...
- NET Core 2.0 微服务跨平台实践
NET Core 2.0 微服务跨平台实践 相关博文: Ubuntu 简单安装 Docker Mac OS.Ubuntu 安装及使用 Consul Consul 服务注册与服务发现 Fabio 安装和 ...
- 在 ASP.NET Core 中执行租户服务
在 ASP.NET Core 中执行租户服务 不定时更新翻译系列,此系列更新毫无时间规律,文笔菜翻译菜求各位看官老爷们轻喷,如觉得我翻译有问题请挪步原博客地址 本博文翻译自: http://gunna ...
- ASP.NET Core 2.0 : 五.服务是如何加载并运行的, Kestrel、配置与环境
"跨平台"后的ASP.Net Core是如何接收并处理请求的呢? 它的运行和处理机制和之前有什么不同? 本章从"宏观"到"微观"地看一下它的 ...
- ASP.NET Core 配置 EF 框架服务 - ASP.NET Core 基础教程 - 简单教程,简单编程
原文:ASP.NET Core 配置 EF 框架服务 - ASP.NET Core 基础教程 - 简单教程,简单编程 ASP.NET Core 配置 EF 框架服务 上一章节中我们了解了 Entity ...
- asp.net core 托管到windows服务,并用iis做反向代理
使用NSSM把.Net Core部署至 Windows 服务 为什么部署至Windows Services 在很多情况下,很少会把.Net Core项目部署至Windows服务中,特别是Asp.n ...
随机推荐
- 几个Graphics函数
1.Graphics.Blit:Copies source texture into destination render texture with a shader 声明: 1.public sta ...
- 做SQL解析时遇到的问题
相对于算术表达式,Json和XML等解析,SQL就比较复杂了. 举个例子来说:符号“(”是目前的SqlAnalyzer1.00中作为进入递归的标志之一,到“)”结束,中间是个查询语句.如果测试用例也是 ...
- 转载:Java的三种取整办法
转载地址:https://blog.csdn.net/maple_fix/article/details/78656152 方法一:向上取整Math.ceil();举例:Math.ceil(11.4) ...
- TG
telegram windous版 安装包 代理 安装好了,却没有网(ssr+PAC) 解决办法 汉化 在telegram 搜索 " zh_cn"
- AWD 第二弹
前言 小组第二次awd训练 万能密码登陆 首页发现一个登录界面,使用万能密码登陆 ' or 1=1#' 登陆成功后,发现Flag 任意文件上传 刚刚的后台界面存在上传按钮,可直接上传一句话木马 文件包 ...
- 纹理过滤模式中的Bilinear、Trilinear以及Anistropic Filtering
1. 为什么在纹理采样时需要texture filter(纹理过滤). 我们的纹理是要贴到三维图形表面的,而三维图形上的pixel中心和纹理上的texel中心并不一至(pixel不一定对应textur ...
- adb安装apk包提示protocol failure问题
截图来自CSDN,待验证
- STL(常用)
STL 简单记录.讲解一些初级阶段常用的用法. STL是C++的一个标准模板库,其中包含了许多在计算机领域常用的基本数据结构以及基本算法.STL主要依赖于模板,使得STL具有广泛的通用性.这篇文章旨在 ...
- python文件的相关操作
python 目录 python 1.python文件的介绍 使用文件的目的 Python文件的类型主要有两种:文本文件和二进制文件. 操作文件的流程主要有三步:打开-操作-关闭操作. 2.文件的打开 ...
- 国产化之路-麒麟V10操作系统安装.net core 3.1 sdk
随着芯片国产化,操作系统国产化,软件国产化的声浪越来越高,公司也已经把开发项目国产化提上了日程,最近搞来了台长城的国产化电脑主机,用来搞试验,安装的是麒麟V10的操作系统,国产化折腾之路就此开始,用的 ...