ASP.NET Web API Model-ModelMetadata

ASP.NET Web API Model-ModelMetadata

前言

前面的几个篇幅主要围绕控制器的执行过程,奈何执行过程中包含的知识点太庞大了,只能一部分一部分的去讲解,在上两篇中我们看到在控制器方法选择器根据请求选定了控制器方法后会生成对应的描述对象之后进入过滤器执行过程中,之后也是我们所讲的在授权过滤器执行之后会执行对Model的系列操作,中间包括Model元数据解析、Model绑定、Model验证,最后会通过Web API框架的独有的方式也就是ParameterBinding参数绑定来执行,在这些操作完毕之后会开始执行行为过滤器,可在控制器方法执行前后进行拦截,从技术方向来说这是AOP思想的一种实现,从业务逻辑上来讲也是一种依赖注入,扯远了,本篇就来谈谈在ASP.NET Web API框架中的Model元数据,也就是ModelMetadata.

 

Model-ModelMetadata(对象介绍、基础知识篇)

在讲解Model绑定之前我还是先来讲解一下在Web API中的Model元数据,在我前面的ASP.NET MVC随笔系列汇总中对MVC框架中的Model的元数据有过讲解,对MVC有了解的朋友可以去看看,在ASP.NET Web API框架中的Model元数据的设计思想和MVC框架中的是一样的,都是对Model进行类型解析,然后已树形结构呈现或者是给需要它的地方使用。为什么说是树形结构呢?我们来看一下示例,后面再结合对象类型这么一讲解大家就差不多该明白了。

 

我们先不管在框架中是怎么表示的,先来看一下定义的Model:

示例代码1-1

    public class EmployeesInfo
    {
        [Display(Description="员工姓名")]
        public string Name { get; set; }

        [Display(Description = "员工年龄")]
        public int Age { get; set; }

        [Display(Description = "员工性别")]
        public string Sex { get; set; }

        [Display(Description = "员工地址信息")]
        public Address AddressInfo { get; set; }
    }

    
    public class Address
    {
        [Display(Description = "地址信息")]
        public string AddressInfo { get; set; }

        [Display(Description = "邮编")]
        public string ZipCode { get; set; }
    }

在代码1-1中我定义了一个Model类型为EmployeesInfo,表示员工的信息,在这其中有几个属性,其中AddressInfo属性是下面的Address类型,这里是为了说明Model元数据所表示的对象存在两种类型,一种是简单类型,另一种是复杂类型。至于这个类型怎么判断下面会说,我们还是先来看示例代码,看下Model经过框架生成为Model元数据过后包含的信息。

示例代码1-2

    public class MetadataController : ApiController
    {
        public string Get()
        {
            EmployeesInfo employeesInfo = new EmployeesInfo()
            {
                Name = "JinYuan",
                Age = 24,
                Sex = "",
                AddressInfo = new Address()
                {
                    AddressInfo = "南京市",
                    ZipCode = "210000"
                }
            };
            ModelMetadata modelMetadata = this.Configuration.Services.GetModelMetadataProvider().GetMetadataForType(()=>employeesInfo, employeesInfo.GetType());
            StringBuilder strBuilder = new StringBuilder();
            ModelMetadataAnalysis(strBuilder, modelMetadata);
            return strBuilder.ToString();

        }

        private void ModelMetadataAnalysis(StringBuilder stringBuilder, ModelMetadata modelMetadata)
        {
            if (modelMetadata.IsComplexType == true)
            {
                foreach (var metadata in modelMetadata.Properties)
                {
                    ModelMetadataAnalysis(stringBuilder, metadata);
                }
            }
            else
            {
                stringBuilder.AppendLine(modelMetadata.Description).AppendLine("Value:" + modelMetadata.Model);
            }
        }
    }

在代码1-2中,我首先定义了一个控制器,并且定义了一个Get()方法,返回类型为string类型,然后在Get()方法中实例化Model,也就是我们代码1-1中的定义,在这之后我根据当前控制器基类所包含的HttpConfiguration类型的属性Configuration,从ServicesContainer 类型的Services属性中获取到Model元数据提供程序,从而使用这个提供程序来根据Model类型生成Model元数据,这里起初生成好的Model元数据也就是modelMetadata变量,它暂时只是一个类型的的表示,也就是EmployeesInfo类型,并且其中并不包含EmployeesInfo类型中属性的元数据,这里要注意的是不包含只是暂时的。

然后下面使用了一个方法ModelMetadataAnalysis(),首先判断当前的Model元数据表示的类型是不是复杂类型,如果是的话就读取这个Model元数据中的Properties属性,注意了在读取的时候,也就是ModelMetadataAnalysis()方法中的参数modelMetadata就会开始使用自身的Model元数据提供程序来生成自身所表示类型下属性的Model元数据。由此可以看到上面的代码实现中只是对简单类型进行了输出。我们看一下示意图。

图1

根据图1所示的这样,然后我们再看代码1-2的实现,最后我们看一下执行的结果。

图2

图2中所示就是从客户端和浏览器共同访问返回的结果值,都是一样的。

 

下面我们来讲解一下相关的对象类型。

图3

我们先来看ModelMetadata类型,从图3中我们可以看到ModelMetadata类型在命名空间System.Web.Http.Metadata下,我们就先来看一下ModelMetadata的定义,

示例代码1-3

    public class ModelMetadata
    {
        public ModelMetadata(ModelMetadataProvider provider, Type containerType, Func<object> modelAccessor, Type modelType, string propertyName);
        public virtual Dictionary<string, object> AdditionalValues { get; }
        public Type ContainerType { get; }
        public virtual bool ConvertEmptyStringToNull { get; set; }
        public virtual string Description { get; set; }
        public virtual bool IsComplexType { get; }
        public bool IsNullableValueType { get; }
        public virtual bool IsReadOnly { get; set; }
        public object Model { get; set; }
        public Type ModelType { get; }
        public virtual IEnumerable<ModelMetadata> Properties { get; }
        public string PropertyName { get; }
        protected ModelMetadataProvider Provider { get; set; }
        public string GetDisplayName();
        public virtual IEnumerable<System.Web.Http.Validation.ModelValidator> GetValidators(IEnumerable<System.Web.Http.Validation.ModelValidatorProvider> validatorProviders);
    }

代码1-3中定义了ModelMetadata类型,我们就从构造函数开始讲解。

在构造函数中有五个参数,这些参数有的是跟属性对应的,就拿第一个参数ModelMetadataProvider类型来说,它对应的就是ModelMetadata类型中的Provider属性,有的朋友会问这个属性干什么的?还记得上面的示例中讲过,在Model元数据是复杂类型的时候从Properties属性中获取当前所表示类型下的Model元数据,就是在获取的时候拿什么生成?就是用的这个Provider属性对应的元数据提供程序,这里也讲一下Properties属性,从它的定义中可以看到,是个IEnumerable<ModelMetadata>类型的属性,也就是为什么上面我所说的以树形结构的方式展现给我们看的原因。下面说到构造函数中的第二个参数实例类型,就是这个元数据对象所表示的、所对应的类型的容器类型,第三个参数比较有意思,是个Func<Object>委托,它是用来获取当前元数据对象所表示类型的实例值,这也就是ModelMetadata类型中Model属性的属性值的由来,第四个参数就是当前元数据对应的对象类型,也就是对应着ModelMetadata类型中ModelType属性值,最后一个参数表示属性名称,也对应着ModelMetadata类型中的PropertyName属性值。

下面说说ModelMetadata类型中的其他属性值,AdditionalValues表示容器属性,可自行添加任何额外值在其中以键值队的方式表示,这个很多框架中设计对象时都会有,可以是object类型,不过这里使用键值队来表示。

IsComplexType属性表示当前Model元数据对象所表示的类型是否是复杂类型,这个怎么判断的呢?

    public virtual bool IsComplexType
    {
        get
        {
            return !TypeHelper.HasStringConverter(this.ModelType);
        }
    }

就是看类型是否可以转换为String类型。

IsReadOnly属性下面再讲,因为在初始化一个Model元数据的时候得到的信息只有这么多,而IsReadOnly属性则是通过ModelAttribute来控制的。

 

我们再来看一下ModelMetadataProvider

示例代码1-4

    public abstract class ModelMetadataProvider
    {
        protected ModelMetadataProvider();
        public abstract IEnumerable<ModelMetadata> GetMetadataForProperties(object container, Type containerType);
        public abstract ModelMetadata GetMetadataForProperty(Func<object> modelAccessor, Type containerType, string propertyName);
        public abstract ModelMetadata GetMetadataForType(Func<object> modelAccessor, Type modelType);
    }

在代码1-4中我们看到Model元数据提供程序ModelMetadataProvider类型中有三个抽象方法,本身也是抽象类,这三个方法的含义来给大家解释一下。

GetMetadataForProperties()方法是根据容器实例、容器的类型来获取容器中所有属性的元数据类型。

GetMetadataForProperty()方法则是根据一个获取容器实例的委托、容器类型,和要返回的属性元数据的属性名称。

GetMetadataForType()方法就是根据一个类型来获取这个类型所所表示的元数据,不过委托参数是要能获取到这个类型的实例。

 

下面我们回到代码1-2中,在我们获取Model元数据提供程序的地方,上面也说过了我们是从哪里获取到的,现在我们就来看看具体的类型,

示例代码1-5

this.SetSingle<ModelMetadataProvider>(new DataAnnotationsModelMetadataProvider());

 

那我们就来看看DataAnnotationsModelMetadataProvider类型中的定义。

示例代码1-6

    public class DataAnnotationsModelMetadataProvider : AssociatedMetadataProvider<CachedDataAnnotationsModelMetadata>
    {
        public DataAnnotationsModelMetadataProvider();

        protected override CachedDataAnnotationsModelMetadata CreateMetadataFromPrototype(CachedDataAnnotationsModelMetadata prototype, Func<object> modelAccessor);
        protected override CachedDataAnnotationsModelMetadata CreateMetadataPrototype(IEnumerable<Attribute> attributes, Type containerType, Type modelType, string propertyName);
    }

我们先不管DataAnnotationsModelMetadataProvider类型,而是看它中定义的函数的返回类型CachedDataAnnotationsModelMetadata类型。

 

CachedDataAnnotationsModelMetadata类型

示例代码1-7

    public class CachedDataAnnotationsModelMetadata : CachedModelMetadata<CachedDataAnnotationsMetadataAttributes>
    {
        public CachedDataAnnotationsModelMetadata(CachedDataAnnotationsModelMetadata prototype, Func<object> modelAccessor);
        public CachedDataAnnotationsModelMetadata(DataAnnotationsModelMetadataProvider provider, Type containerType, Type modelType, string propertyName, IEnumerable<Attribute> attributes);

        [SecuritySafeCritical]
        protected override bool ComputeConvertEmptyStringToNull();
        [SecuritySafeCritical]
        protected override string ComputeDescription();
        [SecuritySafeCritical]
        protected override bool ComputeIsReadOnly();
    }

从代码1-7中我们可以看到CachedDataAnnotationsModelMetadata类型继承自CachedModelMetadata<CachedDataAnnotationsMetadataAttributes>类型,也就是CachedModelMetadata<TPrototypeCache>类型。

让我们先来看一下CachedModelMetadata<TPrototypeCache>类型

代码1-8

public abstract class CachedModelMetadata<TPrototypeCache> : ModelMetadata
    {
        protected CachedModelMetadata(CachedModelMetadata<TPrototypeCache> prototype, Func<object> modelAccessor);
        protected CachedModelMetadata(DataAnnotationsModelMetadataProvider provider, Type containerType, Type modelType, string propertyName, TPrototypeCache prototypeCache);
        public override sealed bool ConvertEmptyStringToNull { get; set; }
        public override sealed string Description { get; set; }
        public override sealed bool IsComplexType { get; }
        public override sealed bool IsReadOnly { get; set; }
        protected TPrototypeCache PrototypeCache { get; set; }

        protected virtual bool ComputeConvertEmptyStringToNull();
        protected virtual string ComputeDescription();
        protected virtual bool ComputeIsComplexType();
        protected virtual bool ComputeIsReadOnly();
    }

看到这里怎么感觉这么乱的呢乱七八糟的,比较烦躁啊!!!!大家莫慌。

前面说到了Model元数据在初始化的时候便会初始了很多的值,可是大家有没有想过Model元数据的真正的作用?在初始化的时候可以说我们的Model元数据已经包含了所表示对象的对象类型和值一些基本的信息,但是我们要给Model上附加额外的动作,比如说控制这个某某属性是只读的或者是让某某属性换一个显示的方式,这种事情是在初始化的时候Model元数据做不了的,那咋整?

看代码1-8中最下面的Compute开头的四个方法,这四个方法就是用来获取我们设置的动作然后设置到Model元数据上的,当然看了一下实现并不是我们想要的结果,而是通过代码1-7中定义的CachedDataAnnotationsModelMetadata类型来实现的,在代码1-7中CachedDataAnnotationsModelMetadata类型继承的是CachedModelMetadata<CachedDataAnnotationsMetadataAttributes>类型,现在我们就来看看CachedDataAnnotationsMetadataAttributes类型的定义。

 

CachedDataAnnotationsMetadataAttributes类型

示例代码1-9

    public class CachedDataAnnotationsMetadataAttributes
    {
        public CachedDataAnnotationsMetadataAttributes(IEnumerable<Attribute> attributes);

        public System.ComponentModel.DataAnnotations.DisplayAttribute Display { get; protected set; }
        public System.ComponentModel.DataAnnotations.DisplayFormatAttribute DisplayFormat { get; protected set; }
        public System.ComponentModel.DataAnnotations.EditableAttribute Editable { get; protected set; }
        public ReadOnlyAttribute ReadOnly { get; protected set; }
    }

代码1-9中的CachedDataAnnotationsMetadataAttributes类型就是应用于元数据特性多个类型的封装,这里我们暂且不管,知道CachedDataAnnotationsMetadataAttributes类型是个啥就行了。现在我们回到代码1-7中看看那几个方法的具体实现方式。

代码1-10

    [SecuritySafeCritical]
    protected override string ComputeDescription()
    {
        if (base.PrototypeCache.Display == null)
        {
            return base.ComputeDescription();
        }
        return base.PrototypeCache.Display.GetDescription();
    }

我挑了其中一个,这里可以清楚的看到是要调用基类当中的PrototypeCache属性,那么这里的PrototypeCache属性对应的是什么类型?

大家可以看一下代码1-8中PrototypeCache属性对应的是什么类型,是一个泛型类型,而在CachedDataAnnotationsModelMetadata类型继承的时候泛型类型我们可以在代码1-7中看到,也就是代码1-9定义的类型。

 

最后我们看下示意图。

图4

最后总结一句在Model元数据初始化的时候便会完成一些初始值的初始化,而对于Model上的行为设置,则需要通过CachedDataAnnotationsMetadataAttributes类型来执行设置了,而CachedDataAnnotationsMetadataAttributes类型中对应的几个属性的类型大家一试便知。


作者:金源

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 2
    评论
AspNetMVC2_RC_VS2008.exe ASP.NET MVC 2 Release Candidate Release Notes This document describes the Release Candidate release of the ASP.NET MVC 2 framework. Installation Notes 2 Documentation 2 Support 2 Upgrading an ASP.NET MVC 1.0 Project to ASP.NET MVC 2 2 New Features 3 ASP.NET MVC validation scripts have been moved to their own file 4 ASP.NET MVC validation scripts can be included at the top or bottom of a page 4 ASP.NET MVC validation scripts support globalization 4 Html.ValidationSummary helper method can display model-level errors 4 T4 templates in Visual Studio generate code that is specific to the target version of the .NET Framework 4 Other Improvements 4 Bug Fixes 5 Breaking Changes 5 Changes in ASP.NET MVC 2 Release Candidate 5 Changes in ASP.NET MVC 2 Beta 6 Changes in ASP.NET MVC 2 Preview 2 6 Changes in ASP.NET MVC 2 Preview 1 7 Known Issues 7 Disclaimer 8 This document describes the Release Candidate release of ASP.NET MVC 2 for Visual Studio 2008 SP1. Installation Notes The ASP.NET MVC 2 Release Candidate for Visual Studio 2008 SP1 can be downloaded from the following page: http://go.microsoft.com/fwlink/?LinkID=157071 ASP.NET MVC 2 can be installed and can run side-by-side with ASP.NET MVC 1.0. Note   Because Visual Studio 2008 and Visual Studio 2010 Beta 2 share a component of ASP.NET MVC 2, installing the ASP.NET MVC 2 Release Candidate release on a computer where Visual Studio 2010 Beta 2 is also installed is not supported. Documentation Documentation for ASP.NET MVC 2, which includes tutorials, technology overviews, code samples, and API reference, is available on the MSDN Web site. A good starting point is the ASP.NET Model View Controller (MVC) topic (http://go.microsoft.com/fwlink/?LinkId=159758). Tutorials and other information about ASP.NET MVC are also available on the ASP.NET Web site (http://www.asp.net/mvc/). Support This is a Release Candidate (RC) release and is not officially supported. If you have questions about working with this release, post them to the ASP.NET MVC forum (http://forums.asp.net/1146.aspx), where members of the ASP.NET community are frequently able to provide informal support. Upgrading an ASP.NET MVC 1.0 Project to ASP.NET MVC 2 To upgrade an existing ASP.NET MVC 1.0 application to version 2, follow these steps: Make a backup of the existing project. Open the project file in a text editor (the file with the .csproj or .vbproj file extension) and find the ProjectTypeGuid element. As the value of that element, replace the GUID {603c0e0b-db56-11dc-be95-000d561079b0} with {F85E285D-A4E0-4152-9332-AB1D724D3325}. When you are done, the value of that element should be as follows: <ProjectTypeGuids>{F85E285D-A4E0-4152-9332-AB1D724D3325};{349c5851-65df-11da-9384-00065b846f21};{fae04ec0-301f-11d3-bf4b-00c04f79efbc}</ProjectTypeGuids> In the Web application root folder, edit the Web.config file. Search for System.Web.Mvc, Version=1.0.0.0 and replace all instances with System.Web.Mvc, Version=2.0.0.0. Repeat the previous step for the Web.config file located in the Views directory. Open the project using Visual Studio, and in Solution Explorer, expand the References node. Delete the reference to System.Web.Mvc (which points to the version 1.0 assembly). Add a reference to System.Web.Mvc (v2.0.0.0). Add the following bindingRedirect element to the Web.config file in the application root under the configuraton section: <runtime> <assemblyBinding xmlns="urn:schemas-microsoft-com:asm.v1"> <dependentAssembly> <assemblyIdentity name="System.Web.Mvc" publicKeyToken="31bf3856ad364e35"/> <bindingRedirect oldVersion="1.0.0.0" newVersion="2.0.0.0"/> </dependentAssembly> </assemblyBinding> </runtime> Create a new ASP.NET MVC 2 application. Copy the files from the Scripts directory of the new application into the Scripts directory of the existing application. Compile the application and run it. If any errors occur, refer to the Breaking Changes section of this document for possible solutions. New Features This section describes features that have been introduced in the ASP.NET MVC 2 Release Candidate release. ASP.NET MVC validation scripts have been moved to their own file To help reduce conflicts with other Ajax libraries, the built-in ASP.NET MVC validation scripts are now in a separate JavaScript file. ASP.NET MVC validation scripts can be included at the top or bottom of a page The server component that renders the client validation metadata now renders the metadata into a JavaScript variable. This allows the validation scripts to be included either at the top of the page or at the bottom, because the scripts will look for the variable and load the metadata when it is available. ASP.NET MVC validation scripts support globalization When the required ASP.NET Ajax globalization scripts are included in a page, the validation logic uses the culture-specific data for data types (such as dates and numbers) when it validates input text. The new Html.GlobalizationScript helper method can be used to render a reference to the globalization script for the current culture. Html.ValidationSummary helper method can display model-level errors Instead of always displaying all validation errors, the Html.ValidationSummary helper method has a new option to display only model-level errors. This enables model-level errors to be displayed in the validation summary and field-specific errors next to each field. T4 templates in Visual Studio generate code that is specific to the target version of the .NET Framework A new property is available to T4 files from the ASP.NET MVC T4 host that specifies the version of the .NET Framework that is used by the application. This allows T4 templates to generate code and markup that is specific to a version of the .NET Framework. In Visual Studio 2008, the value is always .NET 3.5. In Visual Studio 2010, the value is either .NET 3.5 or .NET 4. Other Improvements The following additional changes have been made to existing types and members for the ASP.NET MVC 2 Release Candidate release. The default HTML markup that is generated by the Add View dialog box has been changed to be consistent with the markup that is rendered by the templated helpers (Editor, EditorFor, Display, and DisplayFor). Buttons in a form can specify that they do not cause validation logic to run. The default is that every button in a form causes validation logic to run, and if validation fails, the validation logic blocks submission of the form. Enabling validation selectively for buttons lets you create forms that can post information even if a form is not complete or if the form contains data that is temporarily invalid. Client validators can specify when they run. Possible values are input (while the user is typing), blur (after the user is done typing and moves to another field), and submit (when the user submits the form). Bug Fixes The following bugs have been fixed in the ASP.NET MVC 2 Release Candidate release. The FileResult action result now supports non-US-ASCII characters in file names. Methods and properties of the TempDataDictionary class have been improved to better handle the case when items are flagged for removal from the dictionary. Support for validation in the IDataErrorInfo interface has been fixed. Breaking Changes The following changes might cause errors in existing ASP.NET MVC 1.0 applications. Changes in ASP.NET MVC 2 Release Candidate IIS script mapping script is no longer available in the installer The IIS script mapping script is a command-line script that is used to configure script maps for IIS 6 and for IIS 7 in Classic mode. The script-mapping script is not needed if you use the Visual Studio Development Server or if you use IIS 7 in Integrated mode. The scripts are available as a separate unsupported download on the ASP.NET CodePlex site. The Html.Substitute helper method in MVC Futures is no longer available Due to changes in the rendering behavior of MVC view engines, the Html.Substitute helper method does not work and has been removed. Changes in ASP.NET MVC 2 Beta The IValueProvider interface replaces all uses of IDictionary<string, ValueProviderResult> Every property or method argument that accepted IDictionary<string, ValueProviderResult> now accepts IValueProvider. This change affects only applications that include custom value providers or custom model binders. Examples of properties and methods that are affected by this change include the following: The ValueProvider property of the ControllerBase and ModelBindingContext classes. The TryUpdateModel methods of the Controller class. New CSS classes were added in the Site.css file that are used to style validation messages. Changes in ASP.NET MVC 2 Preview 2 Helpers now return an MvcHtmlString object In order to take advantage of the new HTML-encoding expression syntax in ASP.NET 4, the return type for HTML helpers is now MvcHtmlString instead of a string. Note that if you use ASP.NET MVC 2 and the new helpers with ASP.NET 3.5, you will not be able to take advantage of the HTML-encoding syntax; the new syntax is available only when you run ASP.NET MVC 2 on ASP.NET 4. JsonResult now responds only to HTTP POST requests In order to mitigate JSON hijacking attacks that have the potential for information disclosure, by default, the JsonResult class now responds only to HTTP POST requests. Ajax GET calls to action methods that return a JsonResult object should be changed to use POST instead. If necessary, you can override this behavior by setting the new JsonRequestBehavior property of JsonResult. For more information about the potential exploit, see the blog post JSON Hijacking on Phil Haack’s blog. Model and ModelType property setters on ModelBindingContext are obsolete A new settable ModelMetadata property has been added to the ModelBindingContext class. The new property encapsulates both the Model and the ModelType properties. Although the Model and ModelType properties are obsolete, for backward compatibility the property getters still work; they delegate to the ModelMetadata property to retrieve the value. Changes in ASP.NET MVC 2 Preview 1 Changes to the DefaultControllerFactory class break custom controller factories that derive from it The DefaultControllerFactory class was fixed by removing the RequestContext property. In place of this property, the request context instance is passed to the protected virtual GetControllerInstance and GetControllerType methods. This change affects custom controller factories that derive from DefaultControllerFactory. Custom controller factories are often used to provide dependency injection for ASP.NET MVC applications. To update the custom controller factories to support ASP.NET MVC 2, change the method signature or signatures to match the new signatures, and use the request context parameter instead of the property. “Area” is a now a reserved route-value key The string “area” in Route values now has special meaning in ASP.NET MVC, in the same way that “controller” and “action” do. One implication is that if HTML helpers are supplied with a route-value dictionary containing “area”, the helpers will no longer append “area” in the query string. If you are using the Areas feature, make sure to not use {area} as part of your route URL. Known Issues Adding a debugger watch that shows a value from TempData will mark it for deletion. The side effect was introduced as part of the changes to TempDataDictionary. Disclaimer This is a preliminary document and may be changed substantially prior to final commercial release of the software described herein. The information contained in this document represents the current view of Microsoft Corporation on the issues discussed as of the date of publication. Because Microsoft must respond to changing market conditions, it should not be interpreted to be a commitment on the part of Microsoft, and Microsoft cannot guarantee the accuracy of any information presented after the date of publication. This White Paper is for informational purposes only. MICROSOFT MAKES NO WARRANTIES, EXPRESS, IMPLIED OR STATUTORY, AS TO THE INFORMATION IN THIS DOCUMENT. Complying with all applicable copyright laws is the responsibility of the user. Without limiting the rights under copyright, no part of this document may be reproduced, stored in or introduced into a retrieval system, or transmitted in any form or by any means (electronic, mechanical, photocopying, recording, or otherwise), or for any purpose, without the express written permission of Microsoft Corporation. Microsoft may have patents, patent applications, trademarks, copyrights, or other intellectual property rights covering subject matter in this document. Except as expressly provided in any written license agreement from Microsoft, the furnishing of this document does not give you any license to these patents, trademarks, copyrights, or other intellectual property. Unless otherwise noted, the example companies, organizations, products, domain names, e-mail addresses, logos, people, places and events depicted herein are fictitious, and no association with any real company, organization, product, domain name, email address, logo, person, place or event is intended or should be inferred. © 2009 Microsoft Corporation. All rights reserved. Microsoft and Windows are either registered trademarks or trademarks of Microsoft Corporation in the United States and/or other countries. The names of actual companies and products mentioned herein may be the trademarks of their respective owners.

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论 2
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值