快速入门系列--MVC--04模型
- model元数据
闲来继续学习蒋金楠大师的ASP.NET MVC框架揭秘一书,当前主要阅读的内容是Model元数据的解析,即使是阅读完的现在,仍然有不少细节不是特别明白。好在这部分内容主要是关于Razor引擎的呈现的,通过注解的方式对Model进行自定的修饰,最终使得页面在渲染时(即从cshtml文件转化为html时),相关的数据能够按照指定的形式转化并显示。由于接下来的项目中不再打算使用Razor引擎,该引擎虽然很不错,但也有一些问题,例如存在HTML5代码与HtmlHelper的混写,使得UI层很难与业务代码层完全的分离。所以不太利于程序开发的分工,在当前的互联网需求迅速变化的场景下变得不是也别适合。而且相关工作人员的招聘等问题上也比较难得处理,不可能要求每一个工程师都具有全栈能力,企业也可能负担的其相关成本,自己对微软2012年推出的blend开发模式不太了解,不知道是不是只是支持WPF,WinForm等,可能有些理解上问题,请给予指正。个人观点上,更倾向于直接使用HTML5开发,框架主要负责路由,过滤器等功能,当前携程等网站也主要采用这种方式。因此,该章节算是选学了,主要介绍一些主要的概念。
首先要提及的是元数据,一说元数据,大家第一反应可能都是一样的,就是中间语言IL中对类的描述信息。一般我们可以通过自定义特性的方式对其进行扩充,这儿的Model元数据只要用于控制Model对象(ViewModel对象),在View上的呈现形式。其使用System.Web.Mvc.ModelMetadata来表示Model元数据,并且ModelMetadata是一种迭代,支持自包含的结构,有点像组合模式。如果ModelMetadata是一个复杂类型,可以通过一个相应的XXXTypeConverter辅助类将其转化为简单类型。
接下来,简单介绍一下与该框架相关数据注解特性,框架就是依靠这些注解特性和相应的模板方法来控制Model数据的显示,如下表所示。
特性名称 | |
UIHintAttribute | |
HiddenInputAttribute | ScaffoldColumnAttribute |
DisplayTypeAttribute | DisplayFormatAttribute |
EditableAttribute | ReadOnlyAttribute |
DisplayAttribute | DisplayNameAttribute |
RequiredAttribute | |
AllowHtmlAttribute | |
RadioButtonListAttribute | CheckBoxListAttribute |
DropdownListAttribute | ListBoxAttribute |
相关特性大部分都是顾名思义,比较容易理解,也与EF中的注解特性相似,就不一一介绍了。需要注意的是,如果想自定义一个特性,那么就要实现IMetadataAware接口,与它同名的接口也经常出现在.NET相关数据绑定中,代码如下所示。
[AttributeUsage(AttributeTargets.Class | AttributeTargets.Property)]
public class DisplayTextAttribute : Attribute, IMetadataAware
{
private static Type staticResourceType;
public string DisplayName { get; set; }
public Type ResourceType { get; set; }
public DisplayTextAttribute()
{
this.ResourceType = staticResourceType;
} public void OnMetadataCreated(ModelMetadata metadata)
{
this.DisplayName = this.DisplayName ?? (metadata.PropertyName ?? metadata.ModelType.Name);
if (null == this.ResourceType)
{
metadata.DisplayName = this.DisplayName;
return;
}
PropertyInfo property = this.ResourceType.GetProperty(this.DisplayName, BindingFlags.NonPublic | BindingFlags.Public | BindingFlags.Static);
metadata.DisplayName = property.GetValue(null, null).ToString();
} public static void SetResourceType(Type resourceType)
{
staticResourceType = resourceType;
}
}
Tip:注意在项目中定义相关资源文件,将英文的属性名转化为中文。
常见模板方法为:
模板方法 | ||
HtmlHelper<TModel> | Display | DisplayFor |
Editor | EditorFor | |
DisplayForModel | EditForModel | |
Label | LabelFor | |
DisplayText | DisplayTextFor | |
DropdownList | DropdownListFor | |
ListBox | ListBoxFor |
以上可以很清楚的看到显示模式和编辑模式两种不同的显示形式,由于框架是根据元数据对象中的数据类型属性值去寻找对应模板的,因此将需要将模板的View定义放在EditorTemplates目录下。接下来通过一个表格介绍一些框架预定义的模板。
预定义方法 | ||
EmailAddress | HiddenInput | Html |
Text&String | Url | MultilineText |
Password | Decimal | Boolean |
Collection | Object |
最后用一个简图介绍下与ModelMetadata相关的类加强理解与记忆。
可以看到,ModelMetadata是会进行缓存的,并且通过原型模式进行创建,在.NET中经常可以看到Provider,这儿主要起一个提供者的作用,但与工厂类等其他创建型模式有什么区别仍然不是很清楚,还需要加强理解。
- Model绑定
数据绑定对于长期进行.NET相关开发的技术人员来说,非常的熟悉。无论是最开始的WinForm, WebForm, 还是现在的WPF,ASP.NET MVC,只要是与前台页面数据源相关的内容,都离不开这个概念,该概念的实现极大的简化了相关的开发工作。即使不使用Razor视图引擎,直接使用静态的HTML5页面,该模块仍然不可或缺。现在简单的介绍一下ASP.NET MVC中相关的Model绑定。
在ASP.NET MVC框架中,Model绑定本质上就是为目标Action方法生成参数列表的过程,这些参数列表的来源可能是请求的URL,可能是HTTP的请求头或请求体中,通过参数的元数据信息可以得到相关内容。主要有ControllerDescriptor,ActionDescriptor,ParameterDescriptor三个抽象类,他们均实现了System.Reflection.ICustomAttributeProvider接口,其实主要就是对类元数据信息的包装类,方便元数据信息的使用,其具体实现类与关系如下图所示。
通过上图可以看到,所有Reflected作为前缀的类都是实现类,同时可以看到Controller、Action描述类的异步版本,比较特殊的是TaskAsyncActionDescriptor,它可以在普通的(非异步)的Controller中使用,在自己试图搭建框架时可以模仿该方式,抽象类,同步/异步版本,接下来通过一个表格简要介绍和比较以上三个描述类。
类型 | 简介 |
ControllerDescriptor | 比较特殊是GetFilterAttributes方法,用于获取该控制器上的所有过滤器特性,ActionMethodSelectorAttribute特性包含GET, POST, PUT, DELETE, Head, Options, Patch等七个Http方法。 |
ActionDescriptor | GetFilters方法返回FilterInfo类型,包含ActionFilter,AuthorizationFilter,ExceptionFilter,ResultFilter等四种类型的筛选器,与J2EE类似。 |
ParameterDescriptor | 其中属性ParameterBindingInfo最为关键,实际包含ModelBinder对象,该对象是整个绑定模块的核心,同时Include,Exclude集合用于显示设置参与/不参与绑定的属性,Prefix属性主要用于复杂的类型的绑定。 |
由于数据绑定的来源各不相同,框架通过接口IValueProvider类来统一提供数据。首先介绍NameValueCollectionValueProvider,属于key/Value的形式,需要加强理解的是,在之前介绍的描述一个复杂数据类型的ModelMetadata具有树型层次化结构,而NameValueColletion对象却是一个"扁平"的结构,两者的匹配通过前缀来完成。"扁平化"这个概念在现在数据呈现中出现的非常多,无论是这儿MVC中的ViewModel, 还是WPF中MVVM框架下的ViewModel。该类型的Provider主要包括FormValueProvider和QueryStringProvider,顾名思义,关联HttpRequest中的QueryString属性。
接下来介绍DictionaryValueProvider,与以前数据提供器的主要区别是其数据值不仅支持字符串,还支持任意对象,可以使用泛型约束。主要包括RouteDataValueProvider,HttpFileCollectionProvider,ChildActionValueProvider。这里想重点提及的是ChildActionProvider,它主要用于子Action中,由于子Action不同独立用于响应客户端的请求,只是用于生成部分的HTML。框架中使用ValueProviderFactory工厂类用于创建一系列的值提供器,同时使用ValueProviderFactories这个静态类通过注册的方式管理以上工厂。
之前有提过这部分最重要的类型就是ModelBinder,有了之前的基础,现在是时候介绍它了。借用蒋大师的原话,"Model的绑定体现在从当前请求提取相应的数据并生成相应的对象作为调用目标Action方法的参数列表"。不知道大家还记不记得,之前在ActionDescriptor中提到的ParameterBindingInfo类型的对象,其中就有ModelBinder,为了便于理解仍然使用表格进行介绍。
类型 | 简介 |
IModelBinder | 包含BindModel(ControllerContext controllerContext, ModelBindingContext bindingContext)方法。 |
ModelBinderAttribute | 用于自己定义模型的绑定器,之后会介绍默认绑定器,在未指定绑定时使用,也是最常见的。 |
ModelBinders | 静态类用于注册模型绑定器,可以在Application_Start方法中为指定类型设置绑定器 |
ModelBinderProvider | GetBinder(Type modelType)方法用于根据指定的数据类型获取相应的ModelBinder对象。 |
ModelState | *Model绑定除了设置参数列表以外,还将数据通过ModelState的形式存储于Controller的ViewData中。sw |
ModelBindingContext | 简述Model绑定的过程,首先Action调用ActionInvoker执行Action,此时获得ActionDescriptor对象,然后遍历ActionDescriptor参数列表,根据ParameterDescriptor等对象创建ModelBindingContext。然后获得指定的ModelBinder,调用GetModel得到由ValueProvider提供的相应参数值,最后以ModelState的形式保存。 |
最后,介绍默认的Model绑定DefaultModelBinder类,这块内容比较多,但由于是框架默认提供的,了解即可,主要思路如以下代码所示。
public class DefaultModelBinder : IModelBinder
{
public object BindModel(ControllerContext controllerContext, ModelBindingContext bindingContext)
{
return this.GetModel(controllerContext, bindingContext.ModelType, bindingContext.ValueProvider, bindingContext.ModelName);
} public object GetModel(ControllerContext controllerContext, Type modeType, IValueProvider valueProvider, string key)
{
if (!valueProvider.ContainsPrefix(key))
return null;
return valueProvider.GetValue(key).ConvertTo(modeType);
}
}
以上可以比较明确的看到类型的绑定过程体现在GetModel方法中,所用参数均来自于绑定上下文对象,通过模型名称获得值并转化为指定模型类型,在简单类型的处理上,已完全满足。接下来是复杂类型,通过GetComplexModel方法获得复杂对象,其实也比较好理解,绑定的过程是一个递归的过程,它通过反射根据数据类型创建对象,并将相关值赋到其属性上。每一次的递归都是将属性名称作为前缀附加到现有前缀作为下一次递归的前缀。之后还有数组,集合,字典等类型的绑定,其中都包含一个深复刻的过程,有部分会涉及泛型方法的反射应用,都比较相似就不一一介绍了。
- Model验证
蒋大师的MVC框架解析确实是越学越有趣,即使是跟着学写些示例代码也是收获良多,尤其是关于类型、反射和委托等方面,平时在应用开发中确实很少会有机会写这样的代码。今天学习的ASP.NET MVC中的Model的验证,刚开时会以为这一章会比较简单,因为之前已经学习过了Model元数据的解析、Model绑定,Model的验证可能就只是DataAnnotation相关类的介绍。但实际学习的过程中,尤其是自定义用于修饰Action的验证特性让我到现在仍然感觉是比较萌萌哒,毕竟这一块对于框架的扩展基本上涉及到了验证相关的所有类型。除此之外,昨晚也是我第一次从https://aspnetwebstack.codeplex.com/上用git下载到了到MVC的源码,本以为会比较艰难,但实际却非常的方便,怒赞下。之后在VS2012打开Nuget会自动下载依赖组件,就可以编译通过了。记得今年一直听到各种关于微软的开源计划,自己接触的知识领域还是比较低端,也不太清楚到底有些什么源代码可以看,当时首先想到的就是到目前为止仍然掌握很弱的WCF,然后查查居然也有源码了,顿时觉得压力山大,因为以后再做不好.NET就不能和妈妈说我看不到源码了。原来一直关于.NET的彷徨,至少在这一刻得到很好的坚定,虽然由于市场的原因.NET在国内的发展比较飘忽,但从自身技术发展的角度,有了源码,只要努力,我就可以生活大师的身边,知道什么是对的了,这个一直困惑我多年。不知道大家有没有这样的感受,即使对自己的代码风格、设计理念非常认同,但重来没有说应该这样做的底气。见笑了,言归正传,回到Model的验证,内容比较对,篇幅很能比较长,望见谅。
首先介绍最为核心的ModelValidator抽象类,该类的主要的成员方法包括:GetClientValidationRules(),返回值为客户端验证规则,最终由HtmlHelper的模板方法渲染为html语句,由于未来项目中并不打算使用Razor引擎,这部分会略过一些内容,但之后有一部分关于JQuery-validate组件的扩展还是很有价值的;Validate(object container),返回值为ModelValidationResult集合,需要注意的是该方法的参数container说明验证过程是包含类型本身和其所辖的属性成员的。接下来用图表简要介绍几个MVC中的Model验证解决方案:
验证解决方案 | 简介 |
DataAnnotationsModelValidator | 最主要的验证方案,包括常见的验证特性:RequiredAttribute,RangeAttribute等 |
ClientModelValidator | 客户端验证。 |
DataErrorInfoModelValidator | 实现IDataErrorInfo接口,包括:DataErrorInfoClassModelValidator,DataErrorInfoPropertyModelValidator |
ValidatableObjectAdapter | 实现IValidatableObject接口,也称为"自我验证",比较少使用。 |
这儿仍然使用Provider模式来提供相应的组件,ModelValidatorProvider类具有GetValidators(ModelMetadata metadata, ControllerContext context)
方法,前一个参数描述被验证类型或熟悉的元数据对象,另一个为当前的ControllerContext。同时,具体的Provider与之前介绍的验证解决方案的中类型相对应,在此就不一一介绍,需要注意的是在验证一个类型时,是先验证它的属性,然后才验证它自身,因此会出现验证的短路现象,即属性出错,就不会继续验证和反馈容器类型的错误了。与之前一样,这儿也会使用注册表模式来管理Provider,使用上ModelValidatorProviders来进行注册,框架默认会加载DataAnnotationXXX,ClientXXX,DataErrorInfoPropertyXXX,也可以把自定义的Provider加入其中。在框架中真正负责验证工作的是一个CompositeModelValidator私有类,查看源码确定是ModelValidator中的一个内部类,但为什么这样使用还有一些困惑,为什么这样需要完全隐藏掉该类?
接下来,介绍Model绑定与验证的关系,在前文"Model的绑定"的介绍中提到Controller对象的ViewData包含ModelState集合,用于表示Model的状态,其中既包括ValueProvider提供的值,也包括Errors验证结果。验证结果的呈现通过ValidationMessage,ValidationMessageFor扩展方法对单个属性进行验证,输出html形式为(class="field-validation-error" data-valmsg-for="xxx",data-valmsg-replace="true"),ValidationSummary呈现容器整体的验证结果,可以设置excludePropertyErrors参数。同时注意可以通过ModelState的AddModelError方法添加错误信息,EditorForModel扩展方法在使用时会默认的显示验证错误时的信息。
Model绑定中的验证解释起来比较拗口,但简单说来就是DefaultModelBinder在递归的绑定复杂对象的过程中对绑定后的对象实施验证,如下图所示。
为了更加了解Model绑定和验证的关联,自己跟着蒋大师的源码基本原样敲了一遍,主要里面有一些用法自己还是不够熟悉,多练练了,大家可以无视。
public class CompositeModelValidator : ModelValidator
{
public CompositeModelValidator(ModelMetadata metadata, ControllerContext controllerContext)
: base(metadata, controllerContext)
{
} public override IEnumerable<ModelValidationResult> Validate(object container)
{
bool isPropertiesValid = true;
//验证属性
foreach (var propertyMetadata in Metadata.Properties)
{
foreach (var validator in propertyMetadata.GetValidators(this.ControllerContext))
{
var results = validator.Validate(propertyMetadata.Model);
if (results.Any())
{
isPropertiesValid = false;
}
foreach (var result in results)
{
yield return new ModelValidationResult
{
MemberName = DefaultModelBinder.CreateSubPropertyName(propertyMetadata.PropertyName, result.MemberName),
Message = result.Message
};
}
}
} //验证容器类
if (isPropertiesValid)
{
foreach (var validator in Metadata.GetValidators(this.ControllerContext))
{
var results = validator.Validate(Metadata.Model);
foreach (var result in results)
{
yield return result;
}
}
}
}
}
public class DefaultModelBinder : IModelBinder
{
internal static string CreateSubPropertyName(string prefix, string propertyName)
{
prefix = prefix ?? "";
propertyName = propertyName ?? "";
return (prefix + "." + propertyName).Trim('.');
} protected virtual object GetComplexModel(ControllerContext controllerContext, Type modelType, IValueProvider valueProvider, string prefix) {
object model = CreateModel(modelType);
foreach(PropertyDescriptor property in TypeDescriptor.GetProperties(modelType)){
if (property.IsReadOnly) {
continue;
} string key = string.IsNullOrEmpty(prefix) ? property.Name : prefix + "." + property.Name;
property.SetValue(model, GetModel(controllerContext, property.PropertyType, valueProvider, key));
} //Model验证
var metadata = ModelMetadataProviders.Current.GetMetadataForType(()=>model, modelType);
var validator = new CompositeModelValidator(metadata, controllerContext);
foreach(var result in validator.Validate(model)){
string key = CreateSubPropertyName(prefix, result.MemberName);
controllerContext.Controller.ViewData.ModelState.AddModelError(key, result.Message);
}
return model;
}
}
继续Model验证的学习,顺道提及一下,今天面试的时候又遇到了几个新的问题,包括Code Smith的代码生成工具(类似T4模板,但更方便)、Dapper.net的轻量级ORM框架、服务的幂等性等,将慢慢的学习和分享。
首先介绍最重要的基于验证特性的声明式Model验证,ValidationAttribute是所有验证特性的抽象基类,主要内容如下表所示:
成员变量或函数 | 简介 |
ErrorMessageResourceName | 错误消息所在资源项的名称 |
ErrorMessageResourceType | 错误消息所在资源项的类型 |
IsValid() | 在验证失败时返一个ValidationResult对象 |
GetValidationResult() | 实际调用受保护的IsValid() |
TypeId | 在需要多次使用同一验证特性时需要重写该属性,使得每一次的TypdId不相同。情形:假设需要控制不同级别人员用于不同工资范围,定岗定薪。 |
继承ValidationAttribute的验证特性类如下所示:
特性名称 | |
RequiredAttribute | RangeAttribute |
StringLengthAttribute | Max/MinLengthAttribute |
RegularExpressionAttribute | CompareAttribute |
CustomValidationAttribute |
在验证过程中起作用的是DataAnnotaionsModelValidator对象的Validate方法,首先根据容器对象创建出表示验证上下文的ValidationContext对象,并采用ModelMetadata的DisplayName来做为上下文的名称,然后调用Attribute属性的GetValidatioResult方法进行最终的验证。同时MVC还定义了一个System.Web.Mvc.DataAnnotationsModelValidator<TAttribute>的泛型类,我们常用的RequiredAttribute均继承于该类。
数据特性验证的提供器其包含一个静态的验证工厂集合ValidatableFactories,是一个以类型Type为key,指定委托DataAnnotationsValidatableObjectAdapterFactory为value的字典,比较少见的方式。该提供器的静态构造方法中已将常见特性的验证提供器加入,并提供静态注册方法注册新的验证提供器。之后的内容蒋大师分享了两种扩展,一个是将ValidationAttribute应用在Action的参数上,和J2EE中Spring MVC的方式一致,以及实现同一个Model类型实现多种方式等,就不一一介绍了。
最后,简要介绍客户端验证,在不用Razor引擎的前提下,这部分的主要价值就体现在关于JQuery插件的扩展,关于javascript,提到最多的概念就是PE(Progressive Enhancement)渐进性增强和非入侵式Unobtrusive,最主要对的意思就是页面可以在不支持JS的情况下显示基本内容,再浏览器允许的情况增强显示效果。这里用到了最常见的前端验证框架文件jquery.validate.js,可以通过设置class的内联方式来完成验证,也可以直接通过validate方法来设置验证,代码如下:
$(document).ready(function () {
$("form").validate({
rules: {
name: { required: true },
birthday: { required: true, date: true },
blogAddress: { required: true, url: true },
emailAddress: { required: true, email: true },
},
message: {
name: { required: "请输入姓名" },
birthday: { required: "请输入出生日期", date: "请输入一个合法的日期" },
blogAddress: { required: "请输入姓名", url: "请输入一个合法的URL" },
emailAddress: { required: "请输入Email地址", email: "请输入一个合法的Email地址" },
}
});
});
在框架中,基于JQuery的Model验证其实就是根据数据的验证特性生成相应的js代码,指定的html元素具有"data-val"属性和一系列的以"data-val-"为前缀的属性。并在之后一个<span>元素,该元素的CSS样式为"field-validation-valid",当验证失败时替换为"field-validation-error"。在表示客户端验证的ModelClientValidationRule中,有一个ValidationParameters来表示验证参数名和参数值。之后蒋大师又介绍了一个自定义验证的例子,我只节选出js作为自己学习JQuery插件的练习。
jQuery.validator.addMethod("agerange", function (value, element, params) {
value = value.replace(/(^\s*)(\s*$)/g, "");
if (!value) {
return true;
}
var minAge = params.minage;
var maxAge = params.maxage;
var birthDateArray = value.split("-");
var birthDate = new Date(birthDateArray[0], birthDateArray[1], birthDateArray[2]);
var currentDate = new Date();
var age = currentDate.getFullYear() - birthDate.getFullYear();
return age >= minAge && age <= maxAge;
}); jQuery.validator.unobtrusive.adapters.add("agerange", ["minage", "maxage"], function (options) {
options.rules["agerange"] = {
minage: options.params.minage,
maxage:options.params.maxage
};
options.messages["agerange"] = options.message;
});
注:本文主要供自己学习,不妥之处望见谅。
参考资料:
[1]蒋金楠. ASP.NET MVC4框架揭秘[M]. 上海:电子工业出版社, 2012.
快速入门系列--MVC--04模型的更多相关文章
- 快速入门系列--WebAPI--01基础
ASP.NET MVC和WebAPI已经是.NET Web部分的主流,刚开始时两个公用同一个管道,之后为了更加的轻量化(WebAPI是对WCF Restful的轻量化),WebAPI使用了新的管道,因 ...
- 快速入门系列--WebAPI--03框架你值得拥有
接下来进入的是俺在ASP.NET学习中最重要的WebAPI部分,在现在流行的互联网场景下,WebAPI可以和HTML5.单页应用程序SPA等技术和理念很好的结合在一起.所谓ASP.NET WebAPI ...
- 快速入门系列--WebAPI--04在老版本MVC4下的调整
WebAPI是建立在MVC和WCF的基础上的,原来微软老是喜欢封装的很多,这次终于愿意将http编程模型的相关细节暴露给我们了.在之前的介绍中,基本上都基于.NET 4.5之后版本,其System.N ...
- 快速入门系列--MVC--01概述
虽然使用MVC已经不少年,相关技术的学习进行了多次,但是很多技术思路的理解其实都不够深入.其实就在MVC框架中有很多设计模式和设计思路的体现,例如DependencyResolver类就包含我们常见的 ...
- 快速入门系列--MVC--02路由
现在补上URL路由的学习,至于蒋老师自建的MVC小引擎和相关案例就放在论文提交后再实践咯.通过ASP.NET的路由系统,可以完成请求URL与物理文件的分离,其优点是:灵活性.可读性.SEO优化.接下来 ...
- 快速入门系列--MVC--07与HTML5移动开发的结合
现在移动互联网的盛行,跨平台并兼容不同设备的HTML5越来越盛行,很多公司都在将自己过去的非HTML5网站应用渐进式的转化为HTML5应用,使得一套代码可以兼容不同的物理终端设备和浏览器,极大的提高了 ...
- [转]快速入门系列--WebAPI--01基础
本文转自:http://www.cnblogs.com/wanliwang01/p/aspnet_webapi_base01.html ASP.NET MVC和WebAPI已经是.NET Web部分的 ...
- vue 快速入门 系列 —— vue-cli 下
其他章节请看: vue 快速入门 系列 Vue CLI 4.x 下 在 vue loader 一文中我们已经学会从零搭建一个简单的,用于单文件组件开发的脚手架:本篇,我们将全面学习 vue-cli 这 ...
- es6 快速入门 系列 —— 类 (class)
其他章节请看: es6 快速入门 系列 类 类(class)是 javascript 新特性的一个重要组成部分,这一特性提供了一种更简洁的语法和更好的功能,可以让你通过一个安全.一致的方式来自定义对象 ...
- WPF快速入门系列(4)——深入解析WPF绑定
一.引言 WPF绑定使得原本需要多行代码实现的功能,现在只需要简单的XAML代码就可以完成之前多行后台代码实现的功能.WPF绑定可以理解为一种关系,该关系告诉WPF从一个源对象提取一些信息,并将这些信 ...
随机推荐
- 在Web应用中接入微信支付的流程之极简清晰版
在Web应用中接入微信支付的流程之极简清晰版 背景: 在Web应用中接入微信支付,我以为只是调用几个API稍作调试即可. 没想到微信的API和官方文档里隐坑无数,致我抱着怀疑人生的心情悲愤踩遍了丫们布 ...
- [转] 多进程下数据库环境的恢复:DB_REGISTER
http://www.cnblogs.com/promise6522/archive/2012/05/09/2493542.html
- jquery对象操作
大类 JQ方法 备注 创建元素 var $h1 = $(“<h1>< ...
- C#打开关闭数据库连接
一.忘记sqlserver密码时,运行语句,可修改密码,记得查看账户是否被禁用 EXECUTE sp_password NULL,'输入新密码','sa': 二.代码:data source一定要加上 ...
- linux-10 基本命令之查看内存使用情况- free,history,who,last
free 命令 显示当前系统中内存的使用量情况 格式如下:free[-m/-g] 以m为单位显示当前内存的使用情况 [root@localhost /]# free -m 内存总量 已用量 可用量 ...
- UML学习备忘
两大类UML图: 行为图(behavior diagrams)和结构图(structure diagrams) 行为图将引导系统分析员分析且理清"系统该做些什么"?系统分析 ...
- Dynamic CRM 2013学习笔记 系列汇总
这里列出所有 Dynamic CRM 2013学习笔记 系列文章,方便大家查阅.有任何建议.意见.需要,欢迎大家提交评论一起讨论. 本文原文地址: Dynamic CRM 2013学习笔记 系列汇总 ...
- [Asp.net 开发系列之SignalR篇]专题六:使用SignalR实现消息提醒
一.引言 前面一篇文章我介绍了如何使用SignalR实现图片的传输,然后对于即时通讯应用来说,消息提醒是必不可少的.现在很多网站的都有新消息的提醒功能.自然对于SignalR系列也少不了这个功能的实现 ...
- Oracle Enterprise Manager打不开的解决方法
之前OEM一直可以打开,但今天上班发现打不开了,输入http://localhost:1158/em 提示该网页无法打开. 那么检查一下: cmd进命令行 C:\Documents and Setti ...
- Java虚拟机5:Java垃圾回收(GC)机制详解
哪些内存需要回收? 哪些内存需要回收是垃圾回收机制第一个要考虑的问题,所谓“要回收的垃圾”无非就是那些不可能再被任何途径使用的对象.那么如何找到这些对象? 1.引用计数法 这个算法的实现是,给对象中添 ...