ASP.NET MVC 模型绑定
模型绑定指的是MVC从浏览器发送的HTTP请求中为我们创建.NET对象,在HTTP请求和C#间起着桥梁的作用。模型绑定的一个最简单的例子是带参数的控制器action方法,比如我们注册这样的路径映射:
routes.MapRoute(
name: "Default",
url: "{controller}/{action}/{id}",
defaults: new { controller = "Home", action = "Index",
id = UrlParameter.Optional }
);
控制器Home的Index action带有名为id的参数:
public ActionResult Index(int id) {
Person dataItem = personData.Where(p => p.PersonId == id).First();
return View(dataItem);
}
在我们请求URL“/Home/Index/1”时,默认action调用器ControllerActionInvoker使用模型绑定器为参数id赋值“1”。
默认模型绑定器
模型绑定器实现IModelBinder接口,MVC默认的模型绑定器类名为DefaultModelBinder。它从Request.form、RouteData.Values 、Request.QueryString、Request.Files查找参数值,比如上面例子中的参数id,它在下面路径中搜索:
- Request.Form["id"]
- RouteData.Values["id"]
- Request.QueryString["id"]
- Request.Files["id"]
模型绑定器使用参数的名称搜索可用值,一旦找到一个可以结果搜索即停止。
DefaultModelBinder在参数绑定中同时做类型变换,如果类型转换失败,参数绑定也失败,比如我们请求URL “/Home/Index/apple”会得到int类型不能null的错误,模型绑定器无法将apple转换成整数,视图将null赋值给id引发此错误。我们可以定义id参数为int?,这也只能解决部分问题,在Index方法内我们没有检查id为null的情况,我们可以使用默认参数来彻底解决:
...
public ActionResult Index(int id = 1) {
Person dataItem = personData.Where(p => p.PersonId == id).First();
return View(dataItem);
}
...
实际的应用中我们还需要验证绑定的参数值,比如URL /Home/Index/-1和 /Home/Index/500都可以成功绑定数值到id,但他们超过了集合的上下限。在类型转换时还必须注意文化语言差异,比如日期格式,我们可以使用语言无关的通用格式yyyy-mm-dd。
复杂类型的绑定
上面我们看到的都是绑定到简单c#类型的例子,如果要绑定的模型是类则要复杂的多。以下面的Model类为例:
public class Person {
public int PersonId { get; set; }
public string FirstName { get; set; }
public string LastName { get; set; }
public DateTime BirthDate { get; set; }
public Address HomeAddress { get; set; }
public bool IsApproved { get; set; }
public Role Role { get; set; }
}
public class Address {
public string Line1 { get; set; }
public string Line2 { get; set; }
public string City { get; set; }
public string PostalCode { get; set; }
public string Country { get; set; }
}
public enum Role {
Admin,
User,
Guest
}
创建两个CreatePerson控制器action来获取数据:
public ActionResult CreatePerson() {
return View(new Person());
}
[HttpPost]
public ActionResult CreatePerson(Person model) {
return View("Index", model);
}
这里的action方法参数为复杂类型Person,我们使用Html.EditorFor()帮助函数在视图中创建输入数据的HTML:
@model MvcModels.Models.Person
@{
ViewBag.Title = "CreatePerson";
}
<h2>Create Person</h2>
@using (Html.BeginForm())
{
<div>@Html.LabelFor(m => m.PersonId)@Html.EditorFor(m => m.PersonId)</div>
<div>@Html.LabelFor(m => m.FirstName)@Html.EditorFor(m => m.FirstName)</div>
<div>@Html.LabelFor(m => m.LastName)@Html.EditorFor(m => m.LastName)</div>
<div>@Html.LabelFor(m => m.Role)@Html.EditorFor(m => m.Role)</div>
<div>
@Html.LabelFor(m => m.HomeAddress.City)
@Html.EditorFor(m => m.HomeAddress.City)
</div>
<div>
@Html.LabelFor(m => m.HomeAddress.Country)
@Html.EditorFor(m => m.HomeAddress.Country)
</div>
<button type="submit">Submit</button>
}
使用强类型的EditFor函数能保证生成的HTML元素Name包含模型绑定需要的嵌套前缀,比如HomeAddress.Country,生成的HTML为:
...
<input class="text-box single-line" id="HomeAddress_Country" name="HomeAddress.Country" type="text" value="" />
...
自定义绑定名称前缀
有这样一种情况,我们根据一个对象类型生成HTML,但是希望结果绑定到另外一个对象类型,我们可以通过自定义绑定前缀来实现。比如我们的Model类:
public class AddressSummary {
public string City { get; set; }
public string Country { get; set; }
}
定义一个控制器方法来使用这个Model:
public ActionResult DisplaySummary(AddressSummary summary) {
return View(summary);
}
对应的DisplaySummary.cshtml视图也使用这个Model类:
@model MvcModels.Models.AddressSummary
@{
ViewBag.Title = "DisplaySummary";
}
<h2>Address Summary</h2>
<div><label>City:</label>@Html.DisplayFor(m => m.City)</div>
<div><label>Country:</label>@Html.DisplayFor(m => m.Country)</div>
如果我们从上面编辑Person的视图CreatePerson.cshtml提交到DisplaySummary action:
@model MvcModels.Models.Person
@{
ViewBag.Title = "CreatePerson";
}
<h2>Create Person</h2>
@using(Html.BeginForm("DisplaySummary", "Home")) {
<div>@Html.LabelFor(m => m.PersonId)@Html.EditorFor(m=>m.PersonId)</div>
<div>@Html.LabelFor(m => m.FirstName)@Html.EditorFor(m=>m.FirstName)</div>
<div>@Html.LabelFor(m => m.LastName)@Html.EditorFor(m=>m.LastName)</div>
<div>@Html.LabelFor(m => m.Role)@Html.EditorFor(m=>m.Role)</div>
<div>
@Html.LabelFor(m => m.HomeAddress.City)
@Html.EditorFor(m=> m.HomeAddress.City)
</div>
<div>
@Html.LabelFor(m => m.HomeAddress.Country)
@Html.EditorFor(m=> m.HomeAddress.Country)
</div>
<button type="submit">Submit</button>
}
DisplaySummary视图中将无法正确绑定City和Country,因为CreatePerson中City和Country的input元素名称包含HomeAddress前缀,提交的数据是HomeAddress.City和HomeAddress.Country,而DisplaySummary视图中是不需要这个前缀的。我们可以在控制器方法上通过Bind特性指定绑定前缀来修正:
public ActionResult DisplaySummary([Bind(Prefix="HomeAddress")]AddressSummary summary) {
return View(summary);
}
在Bind特性中我们还可以指定哪个属性不要绑定,比如:
public ActionResult DisplaySummary([Bind(Prefix="HomeAddress", Exclude="Country")]AddressSummary summary) {
return View(summary);
}
这里通过Exclude="Country"禁止Country属性的绑定,与此相对,可以通过Include来指定需要绑定的属性。Bind可以应用在单个action方法上,如果需要更大范围的效果,我们可以直接应用在模型类上:
[Bind(Include="City")]
public class AddressSummary {
public string City { get; set; }
public string Country { get; set; }
}
Bind可以同时应用在Model类和action方法上,一个属性只有在两个地方都没有被排除才会包含在绑定结果中。
绑定到数组和集合
DefaultModelBinder支持数组集合的绑定,比如下面的action方法使用数组作为参数:
public ActionResult Names(string[] names) {
names = names ?? new string[0];
return View(names);
}
视图中我们创建一组同名的input元素:
@model string[]
@{
ViewBag.Title = "Names";
}
<h2>Names</h2>
@if (Model.Length == 0) {
using(Html.BeginForm()) {
for (int i = 0; i < 3; i++) {
<div><label>@(i + 1):</label>@Html.TextBox("names")</div>
}
<button type="submit">Submit</button>
}
} else {
foreach (string str in Model) {
<p>@str</p>
}
@Html.ActionLink("Back", "Names");
}
生成的HTML:
...
<form action="/Home/Names" method="post">
<div><label>1:</label><input id="names" name="names"type="text" value="" /></div>
<div><label>2:</label><input id="names" name="names"type="text" value="" /></div>
<div><label>3:</label><input id="names" name="names"type="text" value="" /></div>
<button type="submit">Submit</button>
</form>
...
提交数据时绑定器从多个names构建一个数组。
上面的例子换成集合是这样的:
public ActionResult Names(IList<string> names) {
names = names ?? new List<string>();
return View(names);
}
视图:
@model IList<string>
@{
ViewBag.Title = "Names";
}
<h2>Names</h2>
@if (Model.Count == 0) {
using(Html.BeginForm()) {
for (int i = 0; i < 3; i++) {
<div><label>@(i + 1):</label>@Html.TextBox("names")</div>
}
<button type="submit">Submit</button>
}
} else {
foreach (string str in Model) {
<p>@str</p>
}
@Html.ActionLink("Back", "Names");
}
如果是要绑定到一个自定义Model类型的集合:
public ActionResult Address(IList<AddressSummary> addresses) {
addresses = addresses ?? new List<AddressSummary>();
return View(addresses);
}
视图:
@using MvcModels.Models
@model IList<AddressSummary>
@{
ViewBag.Title = "Address";
}
<h2>Addresses</h2>
@if (Model.Count() == 0) {
using (Html.BeginForm()) {
for (int i = 0; i < 3; i++) {
<fieldset>
<legend>Address @(i + 1)</legend>
<div><label>City:</label>@Html.Editor("[" + i + "].City")</div>
<div><label>Country:</label>@Html.Editor("[" + i + "].Country")</div>
</fieldset>
}
<button type="submit">Submit</button>
}
} else {
foreach (AddressSummary str in Model) {
<p>@str.City, @str.Country</p>
}
@Html.ActionLink("Back", "Address");
}
生成的HTML表单:
...
<fieldset>
<legend>Address 1</legend>
<div>
<label>City:</label>
<input class="text-box single-line" name="[0].City"type="text" value="" />
</div>
<div>
<label>Country:</label>
<input class="text-box single-line" name="[0].Country"type="text" value="" />
</div>
</fieldset>
<fieldset>
<legend>Address 2</legend>
<div>
<label>City:</label>
<input class="text-box single-line" name="[1].City"type="text" value="" />
</div>
<div>
<label>Country:</label>
<input class="text-box single-line" name="[1].Country"type="text" value="" />
</div>
</fieldset>
...
使用[0]、[1]作为输入元素的名称前缀,绑定器知道需要创建一个集合。
手工调用模型绑定
在请求action方法时MVC自动为我们处理模型绑定,但是我们也可以在代码中手工绑定,这提供了额外的灵活性。我们调用控制器方法UpdateModel手工绑定:
public ActionResult Address() {
IList<AddressSummary> addresses = new List<AddressSummary>();
UpdateModel(addresses);
return View(addresses);
}
我们可以提供UpdateModel额外的参数指定要数据提供者:
public ActionResult Address() {
IList<AddressSummary> addresses = new List<AddressSummary>();
UpdateModel(addresses, new FormValueProvider(ControllerContext));
return View(addresses);
}
参数FormValueProvider指定从Request.Form绑定数据,其他可用的Provider的还有RouteDataValueProvider(RouteData.Values)、QueryStringValueProvider(Request.QueryString)、HttpFileCollectionValueProvider(Request.Files),它们都实现IValueProvider接口,使用控制器类提供的ControllerContext作为构造函数参数。
实际上最常用的限制绑定源的方式是:
public ActionResult Address(FormCollection formData) {
IList<AddressSummary> addresses = new List<AddressSummary>();
UpdateModel(addresses, formData);
return View(addresses);
}
FormCollection为表单数据的键值集合,这是UpdateModel众多重载形式中的一种。
手工数据绑定的另外一个好处是方便我们处理绑定错误:
public ActionResult Address(FormCollection formData) {
IList<AddressSummary> addresses = new List<AddressSummary>();
try {
UpdateModel(addresses, formData);
} catch (InvalidOperationException ex) {
// provide feedback to user
}
return View(addresses);
}
另外一种处理错误的方式是使用TryUpdateModel:
public ActionResult Address(FormCollection formData) {
IList<AddressSummary> addresses = new List<AddressSummary>();
if (TryUpdateModel(addresses, formData)) {
// proceed as normal
} else {
// provide feedback to user
}
return View(addresses);
}
自定义Value Provider
除了上面看到的内建Value provider,我们可以从IValueProvider接口实现自定义的Value provider:
namespace System.Web.Mvc {
public interface IValueProvider {
bool ContainsPrefix(string prefix);
ValueProviderResult GetValue(string key);
}
}
模型绑定器调用ContainsPrefix方法确定value provider是否可以处理提供的名称前缀,GetValue根据传入的键返回可用的参数值,如果没有可用的数据返回null。下面用实例演示如何使用自定义value provider:
public class CountryValueProvider : IValueProvider {
public bool ContainsPrefix(string prefix) {
return prefix.ToLower().IndexOf("country") > -1;
}
public ValueProviderResult GetValue(string key) {
if (ContainsPrefix(key)) {
return new ValueProviderResult("USA", "USA", CultureInfo.InvariantCulture);
} else {
return null;
}
}
}
CountryValueProvider处理任何包含country的属性,对所有包含country名称的属性总是返回“USA”。使用自定义value provider之前还需要创建一个工厂类来创建自动那个有value provider的实例:
public class CustomValueProviderFactory : ValueProviderFactory {
public override IValueProvider GetValueProvider(ControllerContext controllerContext) {
return new CountryValueProvider();
}
}
最后把我们的类工厂在global.asax的application_start中添加到value provider工厂列表中:
public class MvcApplication : System.Web.HttpApplication {
protected void Application_Start() {
AreaRegistration.RegisterAllAreas();
ValueProviderFactories.Factories.Insert(0, new CustomValueProviderFactory());
WebApiConfig.Register(GlobalConfiguration.Configuration);
FilterConfig.RegisterGlobalFilters(GlobalFilters.Filters);
RouteConfig.RegisterRoutes(RouteTable.Routes);
BundleConfig.RegisterBundles(BundleTable.Bundles);
}
}
这里使用ValueProviderFactories.Factories.Insert()将自定义的value provider工厂添加到列表首位以优先使用,当然也可以ValueProviderFactories.Factories.Add()添加到列表末尾。在注册使用这个value provider后,任何对country属性的绑定都会得到值USA。
自定义模型绑定器
除了自定义value provider,我们还可以从IModelBinder接口创建自定义的模型绑定器:
public class AddressSummaryBinder : IModelBinder {
public object BindModel(ControllerContext controllerContext, ModelBindingContext bindingContext) {
AddressSummary model = (AddressSummary)bindingContext.Model ?? new AddressSummary();
model.City = GetValue(bindingContext, "City");
model.Country = GetValue(bindingContext, "Country");
return model;
}
private string GetValue(ModelBindingContext context, string name) {
name = (context.ModelName == "" ? "" : context.ModelName + ".") + name;
ValueProviderResult result = context.ValueProvider.GetValue(name);
if (result == null || result.AttemptedValue == "") {
return "<Not Specified>";
} else {
return (string)result.AttemptedValue;
}
}
}
MVC调用AddressSummaryBinder的BindModel()方法获取模型类型的实例,这里简单的初始化一个AddressSummary实例,调用value provider获取对象属性值,在从value provider获取属性值时我们把添加模型名称ModelBindingContext.ModelName作为属性的前缀。同样,必须在application_start中注册自定义模型绑定器后才能使用:
...
ModelBinders.Binders.Add(typeof(AddressSummary), new AddressSummaryBinder());
...
Dependency Injection和依赖解决器
C#中使用接口可以帮助我们解耦构件, 获取接口的实现我们通常是直接初始化接口的一个实现类:
public class PasswordResetHelper {
public void ResetPassword() {
IEmailSender mySender = new MyEmailSender();
//...call interface methods to configure e-mail details...
mySender.SendEmail();
}
}
使用IEmailSender接口在一定程度上PasswordResetHelper不再要求发送邮件时需要一个具体的邮件发送类,但是直接初始化MyEmailSender使得PasswordResetHelper并没有和MyEmailSender解耦开。我们可以把IEmailSender接口的初始化放到PasswordResetHelper的构造函数上来解决:
public class PasswordResetHelper {
private IEmailSender emailSender;
public PasswordResetHelper(IEmailSender emailSenderParam) {
emailSender = emailSenderParam;
}
public void ResetPassword() {
// ...call interface methods to configure e-mail details...
emailSender.SendEmail();
}
}
但这样带来的问题是如何获取IEmailSender的实现呢?这可以通过运行时Dependency Injection机制来解决,在创建PasswordResetHelper实例时依赖解决器提供一个IEmailSender的实例给PasswordResetHelper构造函数,这种注入方式又称为构造注入。依赖解决器又是怎么知道如何初始化接口的固实实现呢?答案是DI容器,通过在DI容器中注册接口/虚类和对应的实现类将两者联系起来。当然DI不只是DI容器这么简单,还必须考虑类型依赖链条、对象生命周期管理、构造函数参数配置等等问题,好在我们不需要编写自己的容器,微软提供自己的DI容器名为Unity(在nity.codeplex.com获取),而开源的Ninject是个不错的选择。Ninject可以在visual studio中使用nuget包管理器获取并安装,下面就以实例演示如何使用Ninject,我们从接口的定义开始:
using System.Collections.Generic;
namespace EssentialTools.Models {
public interface IValueCalculator {
decimal ValueProducts(IEnumerable<Product> products);
}
}
接口的一个类实现:
using System.Collections.Generic;
using System.Linq; namespace EssentialTools.Models { public class LinqValueCalculator : IValueCalculator {
private IDiscountHelper discounter; public LinqValueCalculator(IDiscountHelper discounterParam) {
discounter = discounterParam;
} public decimal ValueProducts(IEnumerable<Product> products) {
return discounter.ApplyDiscount(products.Sum(p => p.Price));
}
}
}
我们创建一个使用Ninject的自定义依赖解决器:
using System;
using System.Collections.Generic;
using System.Web.Mvc;
using Ninject;
using EssentialTools.Models; namespace EssentialTools.Infrastructure {
public class NinjectDependencyResolver : IDependencyResolver {
private IKernel kernel; public NinjectDependencyResolver() {
kernel = new StandardKernel();
AddBindings();
} public object GetService(Type serviceType) {
return kernel.TryGet(serviceType);
} public IEnumerable<object> GetServices(Type serviceType) {
return kernel.GetAll(serviceType);
} private void AddBindings() {
kernel.Bind<IValueCalculator>().To<LinqValueCalculator>(); }
}
}
这里最重要的是AddBindings方法中的kernel.Bind<IValueCalculator>().To<LinqValueCalculator>(),它将接口IValueCalculator和类实现LinqValueCalculator结合起来,在我们需要接口IValueCalculator的一个实例时,会调用NinjectDependencyResolver的GetService获取到LinqValueCalculator的一个实例。要使NinjectDependencyResolver起作用还必须注册它为应用默认的依赖解决器,这是在application_start中操作:
public class MvcApplication : System.Web.HttpApplication {
protected void Application_Start() {
AreaRegistration.RegisterAllAreas();
DependencyResolver.SetResolver(new NinjectDependencyResolver());
WebApiConfig.Register(GlobalConfiguration.Configuration);
FilterConfig.RegisterGlobalFilters(GlobalFilters.Filters);
RouteConfig.RegisterRoutes(RouteTable.Routes);
}
}
控制器的构造函数中我们传入接口IValueCalculator,依赖解决器会自动为我们创建一个LinqValueCalculator的实例:
public class HomeController : Controller {
private Product[] products = {
new Product {Name = "Kayak", Category = "Watersports", Price = 275M},
new Product {Name = "Lifejacket", Category = "Watersports", Price = 48.95M},
new Product {Name = "Soccer ball", Category = "Soccer", Price = 19.50M},
new Product {Name = "Corner flag", Category = "Soccer", Price = 34.95M}
};
private IValueCalculator calc;
public HomeController(IValueCalculator calcParam) {
calc = calcParam;
}
public ActionResult Index() {
ShoppingCart cart = new ShoppingCart(calc) { Products = products };
decimal totalValue = cart.CalculateProductTotal();
return View(totalValue);
}
}
Ninject的绑定方法非常的灵活:
kernel.Bind<IDiscountHelper>().To<DefaultDiscountHelper>().WithPropertyValue("DiscountSize", 50M); //绑定时指定DefaultDiscountHelper的属性DiscountSize=50
kernel.Bind<IDiscountHelper>().To<DefaultDiscountHelper>().WithConstructorArgument("discountParam", 50M);//绑定时指定DefaultDiscountHelper的构造函数参数discountParam=50
kernel.Bind<IDiscountHelper>().To<FlexibleDiscountHelper>().WhenInjectedInto<LinqValueCalculator>();//条件绑定,在注入到LinqValueCalculator时绑定接口LinqValueCalculator到FlexibleDiscountHelper
除了使用自定义的依赖解决器,我们可以从默认控制器工厂扩展控制器工厂,在自定义控制器工厂中使用Ninject依赖注入:
public class NinjectControllerFactory : DefaultControllerFactory {
private IKernel ninjectKernel;
public NinjectControllerFactory() {
ninjectKernel = new StandardKernel();
AddBindings();
}
protected override IController GetControllerInstance(RequestContext
requestContext, Type controllerType) {
return controllerType == null
? null
: (IController)ninjectKernel.Get(controllerType);
}
private void AddBindings() {
ninjectKernel.Bind<IValueCalculator>().To<LinqValueCalculator>();
}
MVC在获取控制器时调用GetControllerInstance,它使用ninjectKernel.Get(controllerType)来获取相应的控制类实例,同时解决构造注入的问题,比如HomeController的构造函数参数IValueCalculator calcParam,使用这种方式可以限制仅在控制器内注入,控制器外整个应用范围内我们仍然可以使用自定义依赖解决器注入。
需要注意的是依赖解决和注入不是模型绑定的一部分,但它们有一定的相似性,后者解决的action方法上的参数绑定,前者可以说是整个控制器类(构造函数)上的参数绑定(当然不只是用在控制器类上)。
ASP.NET MVC 模型绑定的更多相关文章
- ASP.NET MVC模型绑定的6个建议(转载)
ASP.NET MVC模型绑定的6个建议 发表于2011-08-03 10:25| 来源博客园| 31 条评论| 作者冠军 validationasp.netmvc.netasp 摘要:ASP.NET ...
- ASP.NET没有魔法——ASP.NET MVC 模型绑定
在My Blog中已经有了文章管理功能,可以发布和修改文章,但是对于文章内容来说,这里缺少最重要的排版功能,如果没有排版的博客很大程度上是无法阅读的,由于文章是通过浏览器查看的,所以文章的排版其实与网 ...
- ASP.NET没有魔法——ASP.NET MVC 模型绑定解析(下篇)
上一篇<ASP.NET没有魔法——ASP.NET MVC 模型绑定解析(上篇)>文章介绍了ASP.NET MVC模型绑定的相关组件和概念,本章将介绍Controller在执行时是如何通过这 ...
- asp.net Mvc 模型绑定项目过多会导致页面运行时间卡
asp.net Mvc 模型绑定项目过多会导致页面运行时间卡的问题. 解决方式就是采用ModelView方式进行精简,已减少模型绑定及验证的时间.
- [转] ASP.NET MVC 模型绑定的功能和问题
摘要:本文将与你深入探究 ASP.NET MVC 模型绑定子系统的核心部分,展示模型绑定框架的每一层并提供扩展模型绑定逻辑以满足应用程序需求的各种方法. 同时,你还会看到一些经常被忽视的模型绑定技术, ...
- ASP.NET MVC——模型绑定
这篇文章我们来讲讲模型绑定(Model Binding),其实在初步了解ASP.NET MVC之后,大家可能都会产生一个疑问,为什么URL片段最后会转换为例如int型或者其他类型的参数呢?这里就不得不 ...
- ASP.NET没有魔法——ASP.NET MVC 模型绑定解析(上篇)
前面文章介绍了ASP.NET MVC中的模型绑定和验证功能,本着ASP.NET MVC没有魔法的精神,本章内容将从代码的角度对ASP.NET MVC如何完成模型的绑定和验证进行分析,已了解其原理. 本 ...
- ASP.NET MVC模型绑定1
一.模型绑定原理 模型绑定是指为Controller的Action方法的参数提供值的过程,例如我有一个名为Blog的实体类(准确的说是ViewModel),它有一个名为Title的属性,如果我在VIE ...
- 禁止ASP.NET MVC模型绑定时将空字符串绑定为null
为model添加[DisplayFormat(ConvertEmptyStringToNull = false)] [Display(ResourceType = typeof(AppStrings) ...
随机推荐
- Object C学习笔记9-字符串NSMutableString
NSMutableString类继承自NSString,所以在NSString中的方法在NSMutableString都可以使用. NSMutableString和NSString的区别在于NSMut ...
- Win7搭建FTP服务器
“控制面板” -> “程序和功能” -> “打开或关闭Windows 功能”: 1.展开“Internet 信息服务” 2.勾选“Internet Information Services ...
- 报错:Cannot create PoolableConnectionFactory (The server time zone value 'CST' is unrecognized or represents more than one time zone. You must configure either the server or JDBC driver (via the serverT
报错:Cannot create PoolableConnectionFactory (The server time zone value 'CST' is unrecognized or repr ...
- python log封装
# _*_ coding:utf-8 _*_ import logging import os import sys import time log_path = os.path.dirname(sy ...
- FATAL ERROR: Tried to use mysqladmin in group......
解决办法,#export PATH=$PATH:/usr/bin
- Cinema 4D* 中令人惊叹的体积效果
原文链接 第一部分 第二部分 云.雾.星云.火状结构等体积效果是 3D 美术师日常工作中的常见任务.借助 Turbulence FD* 或 X-Particles* 等当代第三方插件进行流体或粒子模拟 ...
- 阿里路由框架ARouter的使用步骤
ARouter的使用步骤(以宿主APP modulebase和moduleuser 三大模块组成的工程为例) 第一步 因为路由跳转是子模块都需要用到的,所以我们在module_base模块中引入 co ...
- trampoline蹦床函数解决递归调用栈问题
递归函数的调用栈太多,造成溢出,那么只要减少调用栈,就不会溢出.怎么做可以减少调用栈呢?就是采用"循环"换掉"递归". 下面是一个正常的递归函数. functi ...
- 【文章存档】Azure Web 应用如何修改 IIS 配置
链接 https://docs.azure.cn/zh-cn/articles/azure-operations-guide/app-service-web/aog-app-service-web-h ...
- Windows10子系统安装ubuntu+kali渗透环境
Windows10安装子系统ubuntu,安装完ubuntu后再安装katoolin才能使用kali. (katoolin渗透测试的Linux发行版,它可以让你在其他Linux发行版上使用Kali的全 ...