模型绑定指的是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,它在下面路径中搜索:

  1. Request.Form["id"]
  2. RouteData.Values["id"]
  3. Request.QueryString["id"]
  4. Request.Files["id"]

模型绑定器使用参数的名称搜索可用值,一旦找到一个可以结果搜索即停止。

DefaultModelBinder在参数绑定中同时做类型变换,如果类型转换失败,参数绑定也失败,比如我们请求URL “/Home/Index/apple”会得到int类型不能null的错误,模型绑定器无法将apple转换成整数,视图将null赋值给id引发此错误。我们可以定义id参数为int?,这也只能解决部分问题,在Index方法内我们没有检查id为null的情况,我们可以使用默认参数来彻底解决:

...
public ActionResult Index(int id = ) {
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[];
return View(names);
}

视图中我们创建一组同名的input元素:

@model string[]
@{
ViewBag.Title = "Names";
}
<h2>Names</h2>
@if (Model.Length == 0) {
using(Html.BeginForm()) {
for (int i = 0; i <; 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 <; 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() == ) {
using (Html.BeginForm()) {
for (int i = ; i < ; i++) {
<fieldset>
<legend>Address @(i + )</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 </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 </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") > -;
} 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(, 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方法上的参数绑定,前者可以说是整个控制器类(构造函数)上的参数绑定(当然不只是用在控制器类上)。

以上为对《Apress Pro ASP.NET MVC 4》第四版相关内容的总结,不详之处参见原版 http://www.apress.com/9781430242369

ASP.NET MVC 4 (九) 模型绑定的更多相关文章

  1. [转]ASP.NET MVC 4 (九) 模型绑定

    本文转自:http://www.cnblogs.com/duanshuiliu/p/3706701.html 模型绑定指的是MVC从浏览器发送的HTTP请求中为我们创建.NET对象,在HTTP请求和C ...

  2. ASP.NET MVC学习之模型绑定(1)

    一.前言 下面我们将开始学习模型绑定,通过下面的知识我们将能够理解ASP.NET MVC模型的模型绑定器是如何将http请求中的数据转换成模型的,其中我们重点讲述的是表单数据. 二.正文 1.简单类型 ...

  3. Asp.net Mvc 中的模型绑定

    asp.net mvc中的模型绑定可以在提交http请求的时候,进行数据的映射. 1.没有模型绑定的时候 public ActionResult Example0() { ) { string id ...

  4. ASP.NET MVC学习之模型绑定(2)

    3.手工调用模型绑定 很多情况下我们都是通过形参的方式接收来自http流中的数据,这看似是完美的,但是缺少了很多过程中的控制,所以我们就需要使用手工的方式进行绑定.下面我们通过一个例子来说明,首先打开 ...

  5. ASP.NET MVC中的模型绑定

    模型绑定的本质   任何控制器方法的执行都受action invoker组件(下文用invoker代替)控制.对于每个Action方法的参数,这个invoker组件都会获取一个Model Binder ...

  6. ASP.NET MVC 下自定义模型绑定,去除字符串类型前后的空格

    直接贴代码了: SkyModelBinder.cs using System.ComponentModel; using System.Linq; using System.Web.Mvc; name ...

  7. .NET MVC学习之模型绑定

    ASP.NET MVC学习之模型绑定(2)   继ASP.NET MVC学习之模型绑定继续 3.手工调用模型绑定 很多情况下我们都是通过形参的方式接收来自http流中的数据,这看似是完美的,但是缺少了 ...

  8. 【ASP.NET Core】MVC 控制器的模型绑定(宏观篇)

    欢迎来到老周的水文演播中心. 咱们都知道,MVC的控制器也可以用来实现 Web API 的(它们原本就是一个玩意儿),区别嘛也就是一个有 View 而另一个没有 View.于是,在依赖注入的服务容器中 ...

  9. ASP.NET MVC中的模型装配 封装方法 非常好用

    下面说一下 我们知道在asp.net mvc中 视图可以绑定一个实体模型 然后我们三层架构中也有一个model模型 但是这两个很多时候却是不一样的对象来的 就拿微软的官方mvc例子来说明 微软的视图实 ...

随机推荐

  1. 5款替代微软Visio的开源免费软件

    提到流程图和图表设计,自然会想到微软出品的Office Visio,它是一款强大的流程图设计工具.Visio并不在Office标准套装中,需要额外付费购买,这可能会带来某些不便.一方面,并不是所有人都 ...

  2. 报错:bash: pip: command not found

    $ wget https://bootstrap.pypa.io/get-pip.py$ python get-pip.py$ pip -V #查看pip版本

  3. 修改文件夹的protection level之后,哪个job会来执行re-stripe的操作呢?

    有下面的一些job可能参与其中的,他们的描述如下: AutoBalance,AutoBalanceLin - Balances free space in the cluster. The goal ...

  4. js-BootstrapValidator简单使用

    本例使用版本 <!-- 新 Bootstrap 核心 CSS 文件 --> <link href="http://cdn.static.runoob.com/libs/bo ...

  5. Linux中使用Vim快速更换文档中Windows换行符为Linux平台

    一.简述 平时我们把Windows编写好的sh文件放在linux上跑时,经常出现换行符的问题.快速切换的解决方法如下: 二.解决 vim test.sh :set ff? 如果出现fileforma= ...

  6. textAngular字体依赖

    textAngular部分按钮显示不正常, 对比后发现是少了字体css <link href="https://cdn.bootcss.com/font-awesome/4.7.0/c ...

  7. RMAN-05541: no archived logs found in target database

    执行 duplicate target databaseto orcl from active database nofilenamecheck报错如下: RMAN> duplicate tar ...

  8. eclipse default handler IHandler interface “the chosen operation is not enabled”

    NOTE: These two methods: Tip: Subclass AbstractHandler rather than implementing IHandler. but you ca ...

  9. CentOS 7.4安装Nginx 1.14.0

    一.安装所需环境   1.gcc 安装         yum install gcc-c++    

  10. .Net可扩展的单据编号生成器-SNF.CodeRule--SNF快速开发平台3.2

    1.背景 在企业应用中单据编号的自定义是一个很常见的需求,如工号.业务单据编码等,能不能抽象一个通用的框架呢? 2.思路 这里的难点在于实现"解释器",比如将"前缀&qu ...