Welcome to mirror list, hosted at ThFree Co, Russian Federation.

github.com/mono/aspnetwebstack.git - Unnamed repository; edit this file 'description' to name the repository.
summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
authorbradwilson <bradwils@microsoft.com>2012-03-11 21:17:56 +0400
committerbradwilson <bradwils@microsoft.com>2012-03-11 21:17:56 +0400
commit0f8c45fe03e71446fd8287115a1774b549a72314 (patch)
treee26a39eb9ce385aa6993677f44a27446d089c2ff /src/System.Web.Mvc
Initial revision.
Diffstat (limited to 'src/System.Web.Mvc')
-rw-r--r--src/System.Web.Mvc/AcceptVerbsAttribute.cs64
-rw-r--r--src/System.Web.Mvc/ActionDescriptor.cs196
-rw-r--r--src/System.Web.Mvc/ActionDescriptorHelper.cs46
-rw-r--r--src/System.Web.Mvc/ActionExecutedContext.cs42
-rw-r--r--src/System.Web.Mvc/ActionExecutingContext.cs37
-rw-r--r--src/System.Web.Mvc/ActionFilterAttribute.cs25
-rw-r--r--src/System.Web.Mvc/ActionMethodDispatcher.cs79
-rw-r--r--src/System.Web.Mvc/ActionMethodDispatcherCache.cs16
-rw-r--r--src/System.Web.Mvc/ActionMethodSelector.cs116
-rw-r--r--src/System.Web.Mvc/ActionMethodSelectorAttribute.cs10
-rw-r--r--src/System.Web.Mvc/ActionNameAttribute.cs26
-rw-r--r--src/System.Web.Mvc/ActionNameSelectorAttribute.cs10
-rw-r--r--src/System.Web.Mvc/ActionResult.cs7
-rw-r--r--src/System.Web.Mvc/ActionSelector.cs4
-rw-r--r--src/System.Web.Mvc/AdditionalMetaDataAttribute.cs38
-rw-r--r--src/System.Web.Mvc/Ajax/AjaxExtensions.cs358
-rw-r--r--src/System.Web.Mvc/Ajax/AjaxOptions.cs216
-rw-r--r--src/System.Web.Mvc/Ajax/InsertionMode.cs9
-rw-r--r--src/System.Web.Mvc/AjaxHelper.cs83
-rw-r--r--src/System.Web.Mvc/AjaxHelper`1.cs39
-rw-r--r--src/System.Web.Mvc/AjaxRequestExtensions.cs15
-rw-r--r--src/System.Web.Mvc/AllowAnonymousAttribute.cs11
-rw-r--r--src/System.Web.Mvc/AllowHtmlAttribute.cs19
-rw-r--r--src/System.Web.Mvc/AreaHelpers.cs35
-rw-r--r--src/System.Web.Mvc/AreaRegistration.cs54
-rw-r--r--src/System.Web.Mvc/AreaRegistrationContext.cs93
-rw-r--r--src/System.Web.Mvc/AssociatedMetadataProvider.cs110
-rw-r--r--src/System.Web.Mvc/AssociatedValidatorProvider.cs59
-rw-r--r--src/System.Web.Mvc/Async/ActionDescriptorCreator.cs4
-rw-r--r--src/System.Web.Mvc/Async/AsyncActionDescriptor.cs32
-rw-r--r--src/System.Web.Mvc/Async/AsyncActionMethodSelector.cs227
-rw-r--r--src/System.Web.Mvc/Async/AsyncControllerActionInvoker.cs324
-rw-r--r--src/System.Web.Mvc/Async/AsyncManager.cs80
-rw-r--r--src/System.Web.Mvc/Async/AsyncResultWrapper.cs303
-rw-r--r--src/System.Web.Mvc/Async/AsyncUtil.cs31
-rw-r--r--src/System.Web.Mvc/Async/AsyncVoid.cs7
-rw-r--r--src/System.Web.Mvc/Async/BeginInvokeDelegate.cs4
-rw-r--r--src/System.Web.Mvc/Async/EndInvokeDelegate.cs4
-rw-r--r--src/System.Web.Mvc/Async/EndInvokeDelegate`1.cs4
-rw-r--r--src/System.Web.Mvc/Async/IAsyncActionInvoker.cs8
-rw-r--r--src/System.Web.Mvc/Async/IAsyncController.cs10
-rw-r--r--src/System.Web.Mvc/Async/IAsyncManagerContainer.cs7
-rw-r--r--src/System.Web.Mvc/Async/OperationCounter.cs56
-rw-r--r--src/System.Web.Mvc/Async/ReflectedAsyncActionDescriptor.cs188
-rw-r--r--src/System.Web.Mvc/Async/ReflectedAsyncControllerDescriptor.cs104
-rw-r--r--src/System.Web.Mvc/Async/SimpleAsyncResult.cs53
-rw-r--r--src/System.Web.Mvc/Async/SingleEntryGate.cs20
-rw-r--r--src/System.Web.Mvc/Async/SynchronizationContextUtil.cs52
-rw-r--r--src/System.Web.Mvc/Async/SynchronousOperationException.cs30
-rw-r--r--src/System.Web.Mvc/Async/TaskAsyncActionDescriptor.cs264
-rw-r--r--src/System.Web.Mvc/Async/TaskWrapperAsyncResult.cs43
-rw-r--r--src/System.Web.Mvc/Async/Trigger.cs20
-rw-r--r--src/System.Web.Mvc/Async/TriggerListener.cs65
-rw-r--r--src/System.Web.Mvc/AsyncController.cs10
-rw-r--r--src/System.Web.Mvc/AsyncTimeoutAttribute.cs41
-rw-r--r--src/System.Web.Mvc/AuthorizationContext.cs34
-rw-r--r--src/System.Web.Mvc/AuthorizeAttribute.cs152
-rw-r--r--src/System.Web.Mvc/BindAttribute.cs50
-rw-r--r--src/System.Web.Mvc/BuildManagerCompiledView.cs85
-rw-r--r--src/System.Web.Mvc/BuildManagerViewEngine.cs92
-rw-r--r--src/System.Web.Mvc/BuildManagerWrapper.cs34
-rw-r--r--src/System.Web.Mvc/ByteArrayModelBinder.cs34
-rw-r--r--src/System.Web.Mvc/CachedAssociatedMetadataProvider`1.cs94
-rw-r--r--src/System.Web.Mvc/CachedDataAnnotationsMetadataAttributes.cs54
-rw-r--r--src/System.Web.Mvc/CachedDataAnnotationsModelMetadata.cs216
-rw-r--r--src/System.Web.Mvc/CachedDataAnnotationsModelMetadataProvider.cs17
-rw-r--r--src/System.Web.Mvc/CachedModelMetadata`1.cs415
-rw-r--r--src/System.Web.Mvc/CancellationTokenModelBinder.cs12
-rw-r--r--src/System.Web.Mvc/ChildActionOnlyAttribute.cs19
-rw-r--r--src/System.Web.Mvc/ChildActionValueProvider.cs39
-rw-r--r--src/System.Web.Mvc/ChildActionValueProviderFactory.cs15
-rw-r--r--src/System.Web.Mvc/ClientDataTypeModelValidatorProvider.cs159
-rw-r--r--src/System.Web.Mvc/CompareAttribute.cs74
-rw-r--r--src/System.Web.Mvc/ContentResult.cs36
-rw-r--r--src/System.Web.Mvc/Controller.cs920
-rw-r--r--src/System.Web.Mvc/ControllerActionInvoker.cs372
-rw-r--r--src/System.Web.Mvc/ControllerBase.cs130
-rw-r--r--src/System.Web.Mvc/ControllerBuilder.cs88
-rw-r--r--src/System.Web.Mvc/ControllerContext.cs133
-rw-r--r--src/System.Web.Mvc/ControllerDescriptor.cs78
-rw-r--r--src/System.Web.Mvc/ControllerDescriptorCache.cs14
-rw-r--r--src/System.Web.Mvc/ControllerInstanceFilterProvider.cs16
-rw-r--r--src/System.Web.Mvc/ControllerTypeCache.cs138
-rw-r--r--src/System.Web.Mvc/CustomModelBinderAttribute.cs13
-rw-r--r--src/System.Web.Mvc/DataAnnotationsModelMetadata.cs60
-rw-r--r--src/System.Web.Mvc/DataAnnotationsModelMetadataProvider.cs115
-rw-r--r--src/System.Web.Mvc/DataAnnotationsModelValidator.cs67
-rw-r--r--src/System.Web.Mvc/DataAnnotationsModelValidatorProvider.cs375
-rw-r--r--src/System.Web.Mvc/DataAnnotationsModelValidator`1.cs18
-rw-r--r--src/System.Web.Mvc/DataErrorInfoModelValidatorProvider.cs95
-rw-r--r--src/System.Web.Mvc/DataTypeUtil.cs109
-rw-r--r--src/System.Web.Mvc/DefaultControllerFactory.cs294
-rw-r--r--src/System.Web.Mvc/DefaultModelBinder.cs840
-rw-r--r--src/System.Web.Mvc/DefaultViewLocationCache.cs52
-rw-r--r--src/System.Web.Mvc/DependencyResolver.cs210
-rw-r--r--src/System.Web.Mvc/DependencyResolverExtensions.cs18
-rw-r--r--src/System.Web.Mvc/DescriptorUtil.cs86
-rw-r--r--src/System.Web.Mvc/DictionaryHelpers.cs56
-rw-r--r--src/System.Web.Mvc/DictionaryValueProvider`1.cs65
-rw-r--r--src/System.Web.Mvc/DynamicViewDataDictionary.cs47
-rw-r--r--src/System.Web.Mvc/EmptyModelMetadataProvider.cs12
-rw-r--r--src/System.Web.Mvc/EmptyModelValidatorProvider.cs13
-rw-r--r--src/System.Web.Mvc/EmptyResult.cs17
-rw-r--r--src/System.Web.Mvc/Error.cs76
-rw-r--r--src/System.Web.Mvc/ExceptionContext.cs36
-rw-r--r--src/System.Web.Mvc/ExpressionHelper.cs131
-rw-r--r--src/System.Web.Mvc/ExpressionUtil/BinaryExpressionFingerprint.cs41
-rw-r--r--src/System.Web.Mvc/ExpressionUtil/CachedExpressionCompiler.cs142
-rw-r--r--src/System.Web.Mvc/ExpressionUtil/ConditionalExpressionFingerprint.cs28
-rw-r--r--src/System.Web.Mvc/ExpressionUtil/ConstantExpressionFingerprint.cs32
-rw-r--r--src/System.Web.Mvc/ExpressionUtil/DefaultExpressionFingerprint.cs28
-rw-r--r--src/System.Web.Mvc/ExpressionUtil/ExpressionFingerprint.cs47
-rw-r--r--src/System.Web.Mvc/ExpressionUtil/ExpressionFingerprintChain.cs85
-rw-r--r--src/System.Web.Mvc/ExpressionUtil/FingerprintingExpressionVisitor.cs296
-rw-r--r--src/System.Web.Mvc/ExpressionUtil/HashCodeCombiner.cs56
-rw-r--r--src/System.Web.Mvc/ExpressionUtil/Hoisted`2.cs6
-rw-r--r--src/System.Web.Mvc/ExpressionUtil/HoistingExpressionVisitor.cs35
-rw-r--r--src/System.Web.Mvc/ExpressionUtil/IndexExpressionFingerprint.cs41
-rw-r--r--src/System.Web.Mvc/ExpressionUtil/LambdaExpressionFingerprint.cs28
-rw-r--r--src/System.Web.Mvc/ExpressionUtil/MemberExpressionFingerprint.cs38
-rw-r--r--src/System.Web.Mvc/ExpressionUtil/MethodCallExpressionFingerprint.cs41
-rw-r--r--src/System.Web.Mvc/ExpressionUtil/ParameterExpressionFingerprint.cs37
-rw-r--r--src/System.Web.Mvc/ExpressionUtil/TypeBinaryExpressionFingerprint.cs37
-rw-r--r--src/System.Web.Mvc/ExpressionUtil/UnaryExpressionFingerprint.cs41
-rw-r--r--src/System.Web.Mvc/FieldValidationMetadata.cs26
-rw-r--r--src/System.Web.Mvc/FileContentResult.cs26
-rw-r--r--src/System.Web.Mvc/FilePathResult.cs25
-rw-r--r--src/System.Web.Mvc/FileResult.cs143
-rw-r--r--src/System.Web.Mvc/FileStreamResult.cs45
-rw-r--r--src/System.Web.Mvc/Filter.cs34
-rw-r--r--src/System.Web.Mvc/FilterAttribute.cs41
-rw-r--r--src/System.Web.Mvc/FilterAttributeFilterProvider.cs46
-rw-r--r--src/System.Web.Mvc/FilterInfo.cs48
-rw-r--r--src/System.Web.Mvc/FilterProviderCollection.cs125
-rw-r--r--src/System.Web.Mvc/FilterProviders.cs15
-rw-r--r--src/System.Web.Mvc/FilterScope.cs11
-rw-r--r--src/System.Web.Mvc/FormCollection.cs96
-rw-r--r--src/System.Web.Mvc/FormContext.cs81
-rw-r--r--src/System.Web.Mvc/FormMethod.cs8
-rw-r--r--src/System.Web.Mvc/FormValueProvider.cs19
-rw-r--r--src/System.Web.Mvc/FormValueProviderFactory.cs30
-rw-r--r--src/System.Web.Mvc/GlobalFilterCollection.cs61
-rw-r--r--src/System.Web.Mvc/GlobalFilters.cs12
-rw-r--r--src/System.Web.Mvc/GlobalSuppressions.cs19
-rw-r--r--src/System.Web.Mvc/HandleErrorAttribute.cs107
-rw-r--r--src/System.Web.Mvc/HandleErrorInfo.cs33
-rw-r--r--src/System.Web.Mvc/HiddenInputAttribute.cs13
-rw-r--r--src/System.Web.Mvc/Html/ChildActionExtensions.cs181
-rw-r--r--src/System.Web.Mvc/Html/DefaultDisplayTemplates.cs225
-rw-r--r--src/System.Web.Mvc/Html/DefaultEditorTemplates.cs236
-rw-r--r--src/System.Web.Mvc/Html/DisplayExtensions.cs105
-rw-r--r--src/System.Web.Mvc/Html/DisplayNameExtensions.cs60
-rw-r--r--src/System.Web.Mvc/Html/DisplayTextExtensions.cs24
-rw-r--r--src/System.Web.Mvc/Html/EditorExtensions.cs105
-rw-r--r--src/System.Web.Mvc/Html/FormExtensions.cs180
-rw-r--r--src/System.Web.Mvc/Html/InputExtensions.cs581
-rw-r--r--src/System.Web.Mvc/Html/LabelExtensions.cs159
-rw-r--r--src/System.Web.Mvc/Html/LinkExtensions.cs130
-rw-r--r--src/System.Web.Mvc/Html/MvcForm.cs51
-rw-r--r--src/System.Web.Mvc/Html/NameExtensions.cs43
-rw-r--r--src/System.Web.Mvc/Html/PartialExtensions.cs32
-rw-r--r--src/System.Web.Mvc/Html/RenderPartialExtensions.cs29
-rw-r--r--src/System.Web.Mvc/Html/SelectExtensions.cs317
-rw-r--r--src/System.Web.Mvc/Html/TemplateHelpers.cs333
-rw-r--r--src/System.Web.Mvc/Html/TextAreaExtensions.cs192
-rw-r--r--src/System.Web.Mvc/Html/ValidationExtensions.cs390
-rw-r--r--src/System.Web.Mvc/Html/ValueExtensions.cs80
-rw-r--r--src/System.Web.Mvc/HtmlHelper.cs451
-rw-r--r--src/System.Web.Mvc/HtmlHelper`1.cs39
-rw-r--r--src/System.Web.Mvc/HttpDeleteAttribute.cs15
-rw-r--r--src/System.Web.Mvc/HttpFileCollectionValueProvider.cs44
-rw-r--r--src/System.Web.Mvc/HttpFileCollectionValueProviderFactory.cs15
-rw-r--r--src/System.Web.Mvc/HttpGetAttribute.cs15
-rw-r--r--src/System.Web.Mvc/HttpHandlerUtil.cs91
-rw-r--r--src/System.Web.Mvc/HttpNotFoundResult.cs18
-rw-r--r--src/System.Web.Mvc/HttpPostAttribute.cs15
-rw-r--r--src/System.Web.Mvc/HttpPostedFileBaseModelBinder.cs39
-rw-r--r--src/System.Web.Mvc/HttpPutAttribute.cs15
-rw-r--r--src/System.Web.Mvc/HttpRequestExtensions.cs54
-rw-r--r--src/System.Web.Mvc/HttpStatusCodeResult.cs46
-rw-r--r--src/System.Web.Mvc/HttpUnauthorizedResult.cs21
-rw-r--r--src/System.Web.Mvc/HttpVerbs.cs12
-rw-r--r--src/System.Web.Mvc/IActionFilter.cs8
-rw-r--r--src/System.Web.Mvc/IActionInvoker.cs7
-rw-r--r--src/System.Web.Mvc/IAuthorizationFilter.cs7
-rw-r--r--src/System.Web.Mvc/IBuildManager.cs14
-rw-r--r--src/System.Web.Mvc/IClientValidatable.cs19
-rw-r--r--src/System.Web.Mvc/IController.cs9
-rw-r--r--src/System.Web.Mvc/IControllerActivator.cs9
-rw-r--r--src/System.Web.Mvc/IControllerFactory.cs12
-rw-r--r--src/System.Web.Mvc/IDependencyResolver.cs10
-rw-r--r--src/System.Web.Mvc/IEnumerableValueProvider.cs10
-rw-r--r--src/System.Web.Mvc/IExceptionFilter.cs7
-rw-r--r--src/System.Web.Mvc/IFilterProvider.cs9
-rw-r--r--src/System.Web.Mvc/IMetadataAware.cs12
-rw-r--r--src/System.Web.Mvc/IModelBinder.cs7
-rw-r--r--src/System.Web.Mvc/IModelBinderProvider.cs7
-rw-r--r--src/System.Web.Mvc/IMvcControlBuilder.cs7
-rw-r--r--src/System.Web.Mvc/IMvcFilter.cs8
-rw-r--r--src/System.Web.Mvc/IResolver.cs7
-rw-r--r--src/System.Web.Mvc/IResultFilter.cs8
-rw-r--r--src/System.Web.Mvc/IRouteWithArea.cs7
-rw-r--r--src/System.Web.Mvc/ITempDataProvider.cs10
-rw-r--r--src/System.Web.Mvc/IUniquelyIdentifiable.cs7
-rw-r--r--src/System.Web.Mvc/IUnvalidatedRequestValues.cs13
-rw-r--r--src/System.Web.Mvc/IUnvalidatedValueProvider.cs8
-rw-r--r--src/System.Web.Mvc/IValueProvider.cs8
-rw-r--r--src/System.Web.Mvc/IView.cs9
-rw-r--r--src/System.Web.Mvc/IViewDataContainer.cs10
-rw-r--r--src/System.Web.Mvc/IViewEngine.cs9
-rw-r--r--src/System.Web.Mvc/IViewLocationCache.cs8
-rw-r--r--src/System.Web.Mvc/IViewPageActivator.cs7
-rw-r--r--src/System.Web.Mvc/IViewStartPageChild.cs9
-rw-r--r--src/System.Web.Mvc/InputType.cs11
-rw-r--r--src/System.Web.Mvc/JavaScriptResult.cs23
-rw-r--r--src/System.Web.Mvc/JsonRequestBehavior.cs8
-rw-r--r--src/System.Web.Mvc/JsonResult.cs73
-rw-r--r--src/System.Web.Mvc/JsonValueProviderFactory.cs131
-rw-r--r--src/System.Web.Mvc/LinqBinaryModelBinder.cs18
-rw-r--r--src/System.Web.Mvc/ModelBinderAttribute.cs44
-rw-r--r--src/System.Web.Mvc/ModelBinderDictionary.cs167
-rw-r--r--src/System.Web.Mvc/ModelBinderProviderCollection.cs66
-rw-r--r--src/System.Web.Mvc/ModelBinderProviders.cs14
-rw-r--r--src/System.Web.Mvc/ModelBinders.cs71
-rw-r--r--src/System.Web.Mvc/ModelBindingContext.cs138
-rw-r--r--src/System.Web.Mvc/ModelError.cs31
-rw-r--r--src/System.Web.Mvc/ModelErrorCollection.cs18
-rw-r--r--src/System.Web.Mvc/ModelMetadata.cs407
-rw-r--r--src/System.Web.Mvc/ModelMetadataProvider.cs13
-rw-r--r--src/System.Web.Mvc/ModelMetadataProviders.cs29
-rw-r--r--src/System.Web.Mvc/ModelState.cs15
-rw-r--r--src/System.Web.Mvc/ModelStateDictionary.cs180
-rw-r--r--src/System.Web.Mvc/ModelValidationResult.cs20
-rw-r--r--src/System.Web.Mvc/ModelValidator.cs86
-rw-r--r--src/System.Web.Mvc/ModelValidatorProvider.cs9
-rw-r--r--src/System.Web.Mvc/ModelValidatorProviderCollection.cs56
-rw-r--r--src/System.Web.Mvc/ModelValidatorProviders.cs17
-rw-r--r--src/System.Web.Mvc/MultiSelectList.cs118
-rw-r--r--src/System.Web.Mvc/MultiServiceResolver.cs51
-rw-r--r--src/System.Web.Mvc/MvcFilter.cs19
-rw-r--r--src/System.Web.Mvc/MvcHandler.cs248
-rw-r--r--src/System.Web.Mvc/MvcHtmlString.cs28
-rw-r--r--src/System.Web.Mvc/MvcHttpHandler.cs105
-rw-r--r--src/System.Web.Mvc/MvcRouteHandler.cs47
-rw-r--r--src/System.Web.Mvc/MvcWebRazorHostFactory.cs20
-rw-r--r--src/System.Web.Mvc/NameValueCollectionExtensions.cs33
-rw-r--r--src/System.Web.Mvc/NameValueCollectionValueProvider.cs121
-rw-r--r--src/System.Web.Mvc/NoAsyncTimeoutAttribute.cs13
-rw-r--r--src/System.Web.Mvc/NonActionAttribute.cs13
-rw-r--r--src/System.Web.Mvc/NullViewLocationCache.cs18
-rw-r--r--src/System.Web.Mvc/OutputCacheAttribute.cs355
-rw-r--r--src/System.Web.Mvc/ParameterBindingInfo.cs27
-rw-r--r--src/System.Web.Mvc/ParameterDescriptor.cs54
-rw-r--r--src/System.Web.Mvc/ParameterInfoUtil.cs33
-rw-r--r--src/System.Web.Mvc/PartialViewResult.cs28
-rw-r--r--src/System.Web.Mvc/PathHelpers.cs97
-rw-r--r--src/System.Web.Mvc/PreApplicationStartCode.cs26
-rw-r--r--src/System.Web.Mvc/Properties/AssemblyInfo.cs27
-rw-r--r--src/System.Web.Mvc/Properties/MvcResources.Designer.cs1012
-rw-r--r--src/System.Web.Mvc/Properties/MvcResources.resx439
-rw-r--r--src/System.Web.Mvc/QueryStringValueProvider.cs21
-rw-r--r--src/System.Web.Mvc/QueryStringValueProviderFactory.cs30
-rw-r--r--src/System.Web.Mvc/RangeAttributeAdapter.cs19
-rw-r--r--src/System.Web.Mvc/Razor/MvcCSharpRazorCodeGenerator.cs30
-rw-r--r--src/System.Web.Mvc/Razor/MvcCSharpRazorCodeParser.cs68
-rw-r--r--src/System.Web.Mvc/Razor/MvcVBRazorCodeParser.cs93
-rw-r--r--src/System.Web.Mvc/Razor/MvcWebPageRazorHost.cs64
-rw-r--r--src/System.Web.Mvc/Razor/SetModelTypeCodeGenerator.cs47
-rw-r--r--src/System.Web.Mvc/Razor/StartPageLookupDelegate.cs7
-rw-r--r--src/System.Web.Mvc/RazorView.cs82
-rw-r--r--src/System.Web.Mvc/RazorViewEngine.cs85
-rw-r--r--src/System.Web.Mvc/ReaderWriterCache`2.cs66
-rw-r--r--src/System.Web.Mvc/RedirectResult.cs56
-rw-r--r--src/System.Web.Mvc/RedirectToRouteResult.cs77
-rw-r--r--src/System.Web.Mvc/ReflectedActionDescriptor.cs135
-rw-r--r--src/System.Web.Mvc/ReflectedAttributeCache.cs46
-rw-r--r--src/System.Web.Mvc/ReflectedControllerDescriptor.cs99
-rw-r--r--src/System.Web.Mvc/ReflectedParameterBindingInfo.cs62
-rw-r--r--src/System.Web.Mvc/ReflectedParameterDescriptor.cs79
-rw-r--r--src/System.Web.Mvc/RegularExpressionAttributeAdapter.cs18
-rw-r--r--src/System.Web.Mvc/RemoteAttribute.cs139
-rw-r--r--src/System.Web.Mvc/RequireHttpsAttribute.cs38
-rw-r--r--src/System.Web.Mvc/RequiredAttributeAdapter.cs18
-rw-r--r--src/System.Web.Mvc/ResultExecutedContext.cs34
-rw-r--r--src/System.Web.Mvc/ResultExecutingContext.cs28
-rw-r--r--src/System.Web.Mvc/RouteCollectionExtensions.cs193
-rw-r--r--src/System.Web.Mvc/RouteDataValueProvider.cs14
-rw-r--r--src/System.Web.Mvc/RouteDataValueProviderFactory.cs15
-rw-r--r--src/System.Web.Mvc/RouteValuesHelpers.cs60
-rw-r--r--src/System.Web.Mvc/SecurityUtil.cs79
-rw-r--r--src/System.Web.Mvc/SelectList.cs37
-rw-r--r--src/System.Web.Mvc/SelectListItem.cs11
-rw-r--r--src/System.Web.Mvc/SessionStateAttribute.cs15
-rw-r--r--src/System.Web.Mvc/SessionStateTempDataProvider.cs64
-rw-r--r--src/System.Web.Mvc/SingleServiceResolver.cs65
-rw-r--r--src/System.Web.Mvc/StringLengthAttributeAdapter.cs18
-rw-r--r--src/System.Web.Mvc/System.Web.Mvc.csproj475
-rw-r--r--src/System.Web.Mvc/TagBuilderExtensions.cs13
-rw-r--r--src/System.Web.Mvc/TempDataDictionary.cs208
-rw-r--r--src/System.Web.Mvc/TemplateInfo.cs58
-rw-r--r--src/System.Web.Mvc/TryGetValueDelegate.cs4
-rw-r--r--src/System.Web.Mvc/TypeCacheSerializer.cs122
-rw-r--r--src/System.Web.Mvc/TypeCacheUtil.cs104
-rw-r--r--src/System.Web.Mvc/TypeDescriptorHelper.cs13
-rw-r--r--src/System.Web.Mvc/TypeHelpers.cs146
-rw-r--r--src/System.Web.Mvc/UnvalidatedRequestValuesAccessor.cs4
-rw-r--r--src/System.Web.Mvc/UnvalidatedRequestValuesWrapper.cs32
-rw-r--r--src/System.Web.Mvc/UrlHelper.cs222
-rw-r--r--src/System.Web.Mvc/UrlParameter.cs20
-rw-r--r--src/System.Web.Mvc/UrlRewriterHelper.cs45
-rw-r--r--src/System.Web.Mvc/ValidatableObjectAdapter.cs63
-rw-r--r--src/System.Web.Mvc/ValidateAntiForgeryTokenAttribute.cs40
-rw-r--r--src/System.Web.Mvc/ValidateInputAttribute.cs26
-rw-r--r--src/System.Web.Mvc/ValueProviderCollection.cs78
-rw-r--r--src/System.Web.Mvc/ValueProviderDictionary.cs217
-rw-r--r--src/System.Web.Mvc/ValueProviderFactories.cs20
-rw-r--r--src/System.Web.Mvc/ValueProviderFactory.cs7
-rw-r--r--src/System.Web.Mvc/ValueProviderFactoryCollection.cs56
-rw-r--r--src/System.Web.Mvc/ValueProviderResult.cs163
-rw-r--r--src/System.Web.Mvc/ValueProviderUtil.cs119
-rw-r--r--src/System.Web.Mvc/ViewContext.cs281
-rw-r--r--src/System.Web.Mvc/ViewDataDictionary.cs387
-rw-r--r--src/System.Web.Mvc/ViewDataDictionary`1.cs60
-rw-r--r--src/System.Web.Mvc/ViewDataInfo.cs42
-rw-r--r--src/System.Web.Mvc/ViewEngineCollection.cs133
-rw-r--r--src/System.Web.Mvc/ViewEngineResult.cs38
-rw-r--r--src/System.Web.Mvc/ViewEngines.cs16
-rw-r--r--src/System.Web.Mvc/ViewMasterPage.cs68
-rw-r--r--src/System.Web.Mvc/ViewMasterPageControlBuilder.cs18
-rw-r--r--src/System.Web.Mvc/ViewMasterPage`1.cs50
-rw-r--r--src/System.Web.Mvc/ViewPage.cs427
-rw-r--r--src/System.Web.Mvc/ViewPageControlBuilder.cs18
-rw-r--r--src/System.Web.Mvc/ViewPage`1.cs47
-rw-r--r--src/System.Web.Mvc/ViewResult.cs36
-rw-r--r--src/System.Web.Mvc/ViewResultBase.cs105
-rw-r--r--src/System.Web.Mvc/ViewStartPage.cs43
-rw-r--r--src/System.Web.Mvc/ViewTemplateUserControl.cs6
-rw-r--r--src/System.Web.Mvc/ViewTemplateUserControl`1.cs10
-rw-r--r--src/System.Web.Mvc/ViewType.cs19
-rw-r--r--src/System.Web.Mvc/ViewTypeControlBuilder.cs29
-rw-r--r--src/System.Web.Mvc/ViewTypeParserFilter.cs109
-rw-r--r--src/System.Web.Mvc/ViewUserControl.cs213
-rw-r--r--src/System.Web.Mvc/ViewUserControlControlBuilder.cs18
-rw-r--r--src/System.Web.Mvc/ViewUserControl`1.cs58
-rw-r--r--src/System.Web.Mvc/VirtualPathProviderViewEngine.cs345
-rw-r--r--src/System.Web.Mvc/WebFormView.cs72
-rw-r--r--src/System.Web.Mvc/WebFormViewEngine.cs62
-rw-r--r--src/System.Web.Mvc/WebViewPage.cs115
-rw-r--r--src/System.Web.Mvc/WebViewPage`1.cs47
-rw-r--r--src/System.Web.Mvc/packages.config4
350 files changed, 28601 insertions, 0 deletions
diff --git a/src/System.Web.Mvc/AcceptVerbsAttribute.cs b/src/System.Web.Mvc/AcceptVerbsAttribute.cs
new file mode 100644
index 00000000..1783cdfe
--- /dev/null
+++ b/src/System.Web.Mvc/AcceptVerbsAttribute.cs
@@ -0,0 +1,64 @@
+using System.Collections.Generic;
+using System.Collections.ObjectModel;
+using System.Diagnostics.CodeAnalysis;
+using System.Linq;
+using System.Reflection;
+using System.Web.Mvc.Properties;
+
+namespace System.Web.Mvc
+{
+ [SuppressMessage("Microsoft.Design", "CA1019:DefineAccessorsForAttributeArguments", Justification = "The accessor is exposed as an ICollection<string>.")]
+ [AttributeUsage(AttributeTargets.Method, AllowMultiple = false, Inherited = true)]
+ public sealed class AcceptVerbsAttribute : ActionMethodSelectorAttribute
+ {
+ public AcceptVerbsAttribute(HttpVerbs verbs)
+ : this(EnumToArray(verbs))
+ {
+ }
+
+ public AcceptVerbsAttribute(params string[] verbs)
+ {
+ if (verbs == null || verbs.Length == 0)
+ {
+ throw new ArgumentException(MvcResources.Common_NullOrEmpty, "verbs");
+ }
+
+ Verbs = new ReadOnlyCollection<string>(verbs);
+ }
+
+ public ICollection<string> Verbs { get; private set; }
+
+ private static void AddEntryToList(HttpVerbs verbs, HttpVerbs match, List<string> verbList, string entryText)
+ {
+ if ((verbs & match) != 0)
+ {
+ verbList.Add(entryText);
+ }
+ }
+
+ internal static string[] EnumToArray(HttpVerbs verbs)
+ {
+ List<string> verbList = new List<string>();
+
+ AddEntryToList(verbs, HttpVerbs.Get, verbList, "GET");
+ AddEntryToList(verbs, HttpVerbs.Post, verbList, "POST");
+ AddEntryToList(verbs, HttpVerbs.Put, verbList, "PUT");
+ AddEntryToList(verbs, HttpVerbs.Delete, verbList, "DELETE");
+ AddEntryToList(verbs, HttpVerbs.Head, verbList, "HEAD");
+
+ return verbList.ToArray();
+ }
+
+ public override bool IsValidForRequest(ControllerContext controllerContext, MethodInfo methodInfo)
+ {
+ if (controllerContext == null)
+ {
+ throw new ArgumentNullException("controllerContext");
+ }
+
+ string incomingVerb = controllerContext.HttpContext.Request.GetHttpMethodOverride();
+
+ return Verbs.Contains(incomingVerb, StringComparer.OrdinalIgnoreCase);
+ }
+ }
+}
diff --git a/src/System.Web.Mvc/ActionDescriptor.cs b/src/System.Web.Mvc/ActionDescriptor.cs
new file mode 100644
index 00000000..7175bdc4
--- /dev/null
+++ b/src/System.Web.Mvc/ActionDescriptor.cs
@@ -0,0 +1,196 @@
+using System.Collections.Generic;
+using System.ComponentModel;
+using System.Diagnostics.CodeAnalysis;
+using System.Globalization;
+using System.Linq;
+using System.Reflection;
+using System.Web.Mvc.Properties;
+
+namespace System.Web.Mvc
+{
+ public abstract class ActionDescriptor : ICustomAttributeProvider, IUniquelyIdentifiable
+ {
+ private static readonly ActionMethodDispatcherCache _staticDispatcherCache = new ActionMethodDispatcherCache();
+
+ private static readonly ActionSelector[] _emptySelectors = new ActionSelector[0];
+ private readonly Lazy<string> _uniqueId;
+ private ActionMethodDispatcherCache _instanceDispatcherCache;
+
+ protected ActionDescriptor()
+ {
+ _uniqueId = new Lazy<string>(CreateUniqueId);
+ }
+
+ public abstract string ActionName { get; }
+
+ public abstract ControllerDescriptor ControllerDescriptor { get; }
+
+ internal ActionMethodDispatcherCache DispatcherCache
+ {
+ get
+ {
+ if (_instanceDispatcherCache == null)
+ {
+ _instanceDispatcherCache = _staticDispatcherCache;
+ }
+ return _instanceDispatcherCache;
+ }
+ set { _instanceDispatcherCache = value; }
+ }
+
+ [SuppressMessage("Microsoft.Security", "CA2119:SealMethodsThatSatisfyPrivateInterfaces", Justification = "This is overridden elsewhere in System.Web.Mvc")]
+ public virtual string UniqueId
+ {
+ get { return _uniqueId.Value; }
+ }
+
+ private string CreateUniqueId()
+ {
+ return DescriptorUtil.CreateUniqueId(GetType(), ControllerDescriptor, ActionName);
+ }
+
+ public abstract object Execute(ControllerContext controllerContext, IDictionary<string, object> parameters);
+
+ internal static object ExtractParameterFromDictionary(ParameterInfo parameterInfo, IDictionary<string, object> parameters, MethodInfo methodInfo)
+ {
+ object value;
+
+ if (!parameters.TryGetValue(parameterInfo.Name, out value))
+ {
+ // the key should always be present, even if the parameter value is null
+ string message = String.Format(CultureInfo.CurrentCulture, MvcResources.ReflectedActionDescriptor_ParameterNotInDictionary,
+ parameterInfo.Name, parameterInfo.ParameterType, methodInfo, methodInfo.DeclaringType);
+ throw new ArgumentException(message, "parameters");
+ }
+
+ if (value == null && !TypeHelpers.TypeAllowsNullValue(parameterInfo.ParameterType))
+ {
+ // tried to pass a null value for a non-nullable parameter type
+ string message = String.Format(CultureInfo.CurrentCulture, MvcResources.ReflectedActionDescriptor_ParameterCannotBeNull,
+ parameterInfo.Name, parameterInfo.ParameterType, methodInfo, methodInfo.DeclaringType);
+ throw new ArgumentException(message, "parameters");
+ }
+
+ if (value != null && !parameterInfo.ParameterType.IsInstanceOfType(value))
+ {
+ // value was supplied but is not of the proper type
+ string message = String.Format(CultureInfo.CurrentCulture, MvcResources.ReflectedActionDescriptor_ParameterValueHasWrongType,
+ parameterInfo.Name, methodInfo, methodInfo.DeclaringType, value.GetType(), parameterInfo.ParameterType);
+ throw new ArgumentException(message, "parameters");
+ }
+
+ return value;
+ }
+
+ internal static object ExtractParameterOrDefaultFromDictionary(ParameterInfo parameterInfo, IDictionary<string, object> parameters)
+ {
+ Type parameterType = parameterInfo.ParameterType;
+
+ object value;
+ parameters.TryGetValue(parameterInfo.Name, out value);
+
+ // if wrong type, replace with default instance
+ if (parameterType.IsInstanceOfType(value))
+ {
+ return value;
+ }
+ else
+ {
+ object defaultValue;
+ if (ParameterInfoUtil.TryGetDefaultValue(parameterInfo, out defaultValue))
+ {
+ return defaultValue;
+ }
+ else
+ {
+ return TypeHelpers.GetDefaultValue(parameterType);
+ }
+ }
+ }
+
+ public virtual object[] GetCustomAttributes(bool inherit)
+ {
+ return GetCustomAttributes(typeof(object), inherit);
+ }
+
+ public virtual object[] GetCustomAttributes(Type attributeType, bool inherit)
+ {
+ if (attributeType == null)
+ {
+ throw new ArgumentNullException("attributeType");
+ }
+
+ return (object[])Array.CreateInstance(attributeType, 0);
+ }
+
+ public virtual IEnumerable<FilterAttribute> GetFilterAttributes(bool useCache)
+ {
+ return GetCustomAttributes(typeof(FilterAttribute), inherit: true).Cast<FilterAttribute>();
+ }
+
+ [EditorBrowsable(EditorBrowsableState.Never)]
+ [Obsolete("Please call System.Web.Mvc.FilterProviders.Providers.GetFilters() now.", true)]
+ public virtual FilterInfo GetFilters()
+ {
+ return new FilterInfo();
+ }
+
+ public abstract ParameterDescriptor[] GetParameters();
+
+ [SuppressMessage("Microsoft.Design", "CA1024:UsePropertiesWhereAppropriate", Justification = "This method may perform non-trivial work.")]
+ public virtual ICollection<ActionSelector> GetSelectors()
+ {
+ return _emptySelectors;
+ }
+
+ public virtual bool IsDefined(Type attributeType, bool inherit)
+ {
+ if (attributeType == null)
+ {
+ throw new ArgumentNullException("attributeType");
+ }
+
+ return false;
+ }
+
+ internal static string VerifyActionMethodIsCallable(MethodInfo methodInfo)
+ {
+ // we can't call static methods
+ if (methodInfo.IsStatic)
+ {
+ return String.Format(CultureInfo.CurrentCulture,
+ MvcResources.ReflectedActionDescriptor_CannotCallStaticMethod,
+ methodInfo,
+ methodInfo.ReflectedType.FullName);
+ }
+
+ // we can't call instance methods where the 'this' parameter is a type other than ControllerBase
+ if (!typeof(ControllerBase).IsAssignableFrom(methodInfo.ReflectedType))
+ {
+ return String.Format(CultureInfo.CurrentCulture, MvcResources.ReflectedActionDescriptor_CannotCallInstanceMethodOnNonControllerType,
+ methodInfo, methodInfo.ReflectedType.FullName);
+ }
+
+ // we can't call methods with open generic type parameters
+ if (methodInfo.ContainsGenericParameters)
+ {
+ return String.Format(CultureInfo.CurrentCulture, MvcResources.ReflectedActionDescriptor_CannotCallOpenGenericMethods,
+ methodInfo, methodInfo.ReflectedType.FullName);
+ }
+
+ // we can't call methods with ref/out parameters
+ ParameterInfo[] parameterInfos = methodInfo.GetParameters();
+ foreach (ParameterInfo parameterInfo in parameterInfos)
+ {
+ if (parameterInfo.IsOut || parameterInfo.ParameterType.IsByRef)
+ {
+ return String.Format(CultureInfo.CurrentCulture, MvcResources.ReflectedActionDescriptor_CannotCallMethodsWithOutOrRefParameters,
+ methodInfo, methodInfo.ReflectedType.FullName, parameterInfo);
+ }
+ }
+
+ // we can call this method
+ return null;
+ }
+ }
+}
diff --git a/src/System.Web.Mvc/ActionDescriptorHelper.cs b/src/System.Web.Mvc/ActionDescriptorHelper.cs
new file mode 100644
index 00000000..1d659c0e
--- /dev/null
+++ b/src/System.Web.Mvc/ActionDescriptorHelper.cs
@@ -0,0 +1,46 @@
+using System.Collections.Generic;
+using System.Reflection;
+
+namespace System.Web.Mvc
+{
+ internal static class ActionDescriptorHelper
+ {
+ public static ICollection<ActionSelector> GetSelectors(MethodInfo methodInfo)
+ {
+ ActionMethodSelectorAttribute[] attrs = (ActionMethodSelectorAttribute[])methodInfo.GetCustomAttributes(typeof(ActionMethodSelectorAttribute), inherit: true);
+ ActionSelector[] selectors = Array.ConvertAll(attrs, attr => (ActionSelector)(controllerContext => attr.IsValidForRequest(controllerContext, methodInfo)));
+ return selectors;
+ }
+
+ public static bool IsDefined(MemberInfo methodInfo, Type attributeType, bool inherit)
+ {
+ return methodInfo.IsDefined(attributeType, inherit);
+ }
+
+ public static object[] GetCustomAttributes(MemberInfo methodInfo, bool inherit)
+ {
+ return methodInfo.GetCustomAttributes(inherit);
+ }
+
+ public static object[] GetCustomAttributes(MemberInfo methodInfo, Type attributeType, bool inherit)
+ {
+ return methodInfo.GetCustomAttributes(attributeType, inherit);
+ }
+
+ public static ParameterDescriptor[] GetParameters(ActionDescriptor actionDescriptor, MethodInfo methodInfo, ref ParameterDescriptor[] parametersCache)
+ {
+ ParameterDescriptor[] parameters = LazilyFetchParametersCollection(actionDescriptor, methodInfo, ref parametersCache);
+
+ // need to clone array so that user modifications aren't accidentally stored
+ return (ParameterDescriptor[])parameters.Clone();
+ }
+
+ private static ParameterDescriptor[] LazilyFetchParametersCollection(ActionDescriptor actionDescriptor, MethodInfo methodInfo, ref ParameterDescriptor[] parametersCache)
+ {
+ return DescriptorUtil.LazilyFetchOrCreateDescriptors<ParameterInfo, ParameterDescriptor>(
+ cacheLocation: ref parametersCache,
+ initializer: methodInfo.GetParameters,
+ converter: parameterInfo => new ReflectedParameterDescriptor(parameterInfo, actionDescriptor));
+ }
+ }
+}
diff --git a/src/System.Web.Mvc/ActionExecutedContext.cs b/src/System.Web.Mvc/ActionExecutedContext.cs
new file mode 100644
index 00000000..0ed6b178
--- /dev/null
+++ b/src/System.Web.Mvc/ActionExecutedContext.cs
@@ -0,0 +1,42 @@
+using System.Diagnostics.CodeAnalysis;
+
+namespace System.Web.Mvc
+{
+ public class ActionExecutedContext : ControllerContext
+ {
+ private ActionResult _result;
+
+ // parameterless constructor used for mocking
+ public ActionExecutedContext()
+ {
+ }
+
+ [SuppressMessage("Microsoft.Usage", "CA2214:DoNotCallOverridableMethodsInConstructors", Justification = "The virtual property setters are only to support mocking frameworks, in which case this constructor shouldn't be called anyway.")]
+ public ActionExecutedContext(ControllerContext controllerContext, ActionDescriptor actionDescriptor, bool canceled, Exception exception)
+ : base(controllerContext)
+ {
+ if (actionDescriptor == null)
+ {
+ throw new ArgumentNullException("actionDescriptor");
+ }
+
+ ActionDescriptor = actionDescriptor;
+ Canceled = canceled;
+ Exception = exception;
+ }
+
+ public virtual ActionDescriptor ActionDescriptor { get; set; }
+
+ public virtual bool Canceled { get; set; }
+
+ public virtual Exception Exception { get; set; }
+
+ public bool ExceptionHandled { get; set; }
+
+ public ActionResult Result
+ {
+ get { return _result ?? EmptyResult.Instance; }
+ set { _result = value; }
+ }
+ }
+}
diff --git a/src/System.Web.Mvc/ActionExecutingContext.cs b/src/System.Web.Mvc/ActionExecutingContext.cs
new file mode 100644
index 00000000..1a66c62c
--- /dev/null
+++ b/src/System.Web.Mvc/ActionExecutingContext.cs
@@ -0,0 +1,37 @@
+using System.Collections.Generic;
+using System.Diagnostics.CodeAnalysis;
+
+namespace System.Web.Mvc
+{
+ public class ActionExecutingContext : ControllerContext
+ {
+ // parameterless constructor used for mocking
+ public ActionExecutingContext()
+ {
+ }
+
+ [SuppressMessage("Microsoft.Usage", "CA2214:DoNotCallOverridableMethodsInConstructors", Justification = "The virtual property setters are only to support mocking frameworks, in which case this constructor shouldn't be called anyway.")]
+ public ActionExecutingContext(ControllerContext controllerContext, ActionDescriptor actionDescriptor, IDictionary<string, object> actionParameters)
+ : base(controllerContext)
+ {
+ if (actionDescriptor == null)
+ {
+ throw new ArgumentNullException("actionDescriptor");
+ }
+ if (actionParameters == null)
+ {
+ throw new ArgumentNullException("actionParameters");
+ }
+
+ ActionDescriptor = actionDescriptor;
+ ActionParameters = actionParameters;
+ }
+
+ public virtual ActionDescriptor ActionDescriptor { get; set; }
+
+ [SuppressMessage("Microsoft.Usage", "CA2227:CollectionPropertiesShouldBeReadOnly", Justification = "The property setter is only here to support mocking this type and should not be called at runtime.")]
+ public virtual IDictionary<string, object> ActionParameters { get; set; }
+
+ public ActionResult Result { get; set; }
+ }
+}
diff --git a/src/System.Web.Mvc/ActionFilterAttribute.cs b/src/System.Web.Mvc/ActionFilterAttribute.cs
new file mode 100644
index 00000000..a27c5fa3
--- /dev/null
+++ b/src/System.Web.Mvc/ActionFilterAttribute.cs
@@ -0,0 +1,25 @@
+namespace System.Web.Mvc
+{
+ [AttributeUsage(AttributeTargets.Class | AttributeTargets.Method, Inherited = true, AllowMultiple = false)]
+ public abstract class ActionFilterAttribute : FilterAttribute, IActionFilter, IResultFilter
+ {
+ // The OnXxx() methods are virtual rather than abstract so that a developer need override
+ // only the ones that interest him.
+
+ public virtual void OnActionExecuting(ActionExecutingContext filterContext)
+ {
+ }
+
+ public virtual void OnActionExecuted(ActionExecutedContext filterContext)
+ {
+ }
+
+ public virtual void OnResultExecuting(ResultExecutingContext filterContext)
+ {
+ }
+
+ public virtual void OnResultExecuted(ResultExecutedContext filterContext)
+ {
+ }
+ }
+}
diff --git a/src/System.Web.Mvc/ActionMethodDispatcher.cs b/src/System.Web.Mvc/ActionMethodDispatcher.cs
new file mode 100644
index 00000000..3fc3a0b9
--- /dev/null
+++ b/src/System.Web.Mvc/ActionMethodDispatcher.cs
@@ -0,0 +1,79 @@
+using System.Collections.Generic;
+using System.Linq.Expressions;
+using System.Reflection;
+
+namespace System.Web.Mvc
+{
+ // The methods in this class don't perform error checking; that is the responsibility of the
+ // caller.
+ internal sealed class ActionMethodDispatcher
+ {
+ private ActionExecutor _executor;
+
+ public ActionMethodDispatcher(MethodInfo methodInfo)
+ {
+ _executor = GetExecutor(methodInfo);
+ MethodInfo = methodInfo;
+ }
+
+ private delegate object ActionExecutor(ControllerBase controller, object[] parameters);
+
+ private delegate void VoidActionExecutor(ControllerBase controller, object[] parameters);
+
+ public MethodInfo MethodInfo { get; private set; }
+
+ public object Execute(ControllerBase controller, object[] parameters)
+ {
+ return _executor(controller, parameters);
+ }
+
+ private static ActionExecutor GetExecutor(MethodInfo methodInfo)
+ {
+ // Parameters to executor
+ ParameterExpression controllerParameter = Expression.Parameter(typeof(ControllerBase), "controller");
+ ParameterExpression parametersParameter = Expression.Parameter(typeof(object[]), "parameters");
+
+ // Build parameter list
+ List<Expression> parameters = new List<Expression>();
+ ParameterInfo[] paramInfos = methodInfo.GetParameters();
+ for (int i = 0; i < paramInfos.Length; i++)
+ {
+ ParameterInfo paramInfo = paramInfos[i];
+ BinaryExpression valueObj = Expression.ArrayIndex(parametersParameter, Expression.Constant(i));
+ UnaryExpression valueCast = Expression.Convert(valueObj, paramInfo.ParameterType);
+
+ // valueCast is "(Ti) parameters[i]"
+ parameters.Add(valueCast);
+ }
+
+ // Call method
+ UnaryExpression instanceCast = (!methodInfo.IsStatic) ? Expression.Convert(controllerParameter, methodInfo.ReflectedType) : null;
+ MethodCallExpression methodCall = methodCall = Expression.Call(instanceCast, methodInfo, parameters);
+
+ // methodCall is "((TController) controller) method((T0) parameters[0], (T1) parameters[1], ...)"
+ // Create function
+ if (methodCall.Type == typeof(void))
+ {
+ Expression<VoidActionExecutor> lambda = Expression.Lambda<VoidActionExecutor>(methodCall, controllerParameter, parametersParameter);
+ VoidActionExecutor voidExecutor = lambda.Compile();
+ return WrapVoidAction(voidExecutor);
+ }
+ else
+ {
+ // must coerce methodCall to match ActionExecutor signature
+ UnaryExpression castMethodCall = Expression.Convert(methodCall, typeof(object));
+ Expression<ActionExecutor> lambda = Expression.Lambda<ActionExecutor>(castMethodCall, controllerParameter, parametersParameter);
+ return lambda.Compile();
+ }
+ }
+
+ private static ActionExecutor WrapVoidAction(VoidActionExecutor executor)
+ {
+ return delegate(ControllerBase controller, object[] parameters)
+ {
+ executor(controller, parameters);
+ return null;
+ };
+ }
+ }
+}
diff --git a/src/System.Web.Mvc/ActionMethodDispatcherCache.cs b/src/System.Web.Mvc/ActionMethodDispatcherCache.cs
new file mode 100644
index 00000000..cdfd57af
--- /dev/null
+++ b/src/System.Web.Mvc/ActionMethodDispatcherCache.cs
@@ -0,0 +1,16 @@
+using System.Reflection;
+
+namespace System.Web.Mvc
+{
+ internal sealed class ActionMethodDispatcherCache : ReaderWriterCache<MethodInfo, ActionMethodDispatcher>
+ {
+ public ActionMethodDispatcherCache()
+ {
+ }
+
+ public ActionMethodDispatcher GetDispatcher(MethodInfo methodInfo)
+ {
+ return FetchOrCreateItem(methodInfo, () => new ActionMethodDispatcher(methodInfo));
+ }
+ }
+}
diff --git a/src/System.Web.Mvc/ActionMethodSelector.cs b/src/System.Web.Mvc/ActionMethodSelector.cs
new file mode 100644
index 00000000..834b296a
--- /dev/null
+++ b/src/System.Web.Mvc/ActionMethodSelector.cs
@@ -0,0 +1,116 @@
+using System.Collections.Generic;
+using System.Globalization;
+using System.Linq;
+using System.Reflection;
+using System.Text;
+using System.Web.Mvc.Properties;
+
+namespace System.Web.Mvc
+{
+ internal sealed class ActionMethodSelector
+ {
+ public ActionMethodSelector(Type controllerType)
+ {
+ ControllerType = controllerType;
+ PopulateLookupTables();
+ }
+
+ public Type ControllerType { get; private set; }
+
+ public MethodInfo[] AliasedMethods { get; private set; }
+
+ public ILookup<string, MethodInfo> NonAliasedMethods { get; private set; }
+
+ private AmbiguousMatchException CreateAmbiguousMatchException(List<MethodInfo> ambiguousMethods, string actionName)
+ {
+ StringBuilder exceptionMessageBuilder = new StringBuilder();
+ foreach (MethodInfo methodInfo in ambiguousMethods)
+ {
+ string controllerAction = Convert.ToString(methodInfo, CultureInfo.CurrentCulture);
+ string controllerType = methodInfo.DeclaringType.FullName;
+ exceptionMessageBuilder.AppendLine();
+ exceptionMessageBuilder.AppendFormat(CultureInfo.CurrentCulture, MvcResources.ActionMethodSelector_AmbiguousMatchType, controllerAction, controllerType);
+ }
+ string message = String.Format(CultureInfo.CurrentCulture, MvcResources.ActionMethodSelector_AmbiguousMatch,
+ actionName, ControllerType.Name, exceptionMessageBuilder);
+ return new AmbiguousMatchException(message);
+ }
+
+ public MethodInfo FindActionMethod(ControllerContext controllerContext, string actionName)
+ {
+ List<MethodInfo> methodsMatchingName = GetMatchingAliasedMethods(controllerContext, actionName);
+ methodsMatchingName.AddRange(NonAliasedMethods[actionName]);
+ List<MethodInfo> finalMethods = RunSelectionFilters(controllerContext, methodsMatchingName);
+
+ switch (finalMethods.Count)
+ {
+ case 0:
+ return null;
+
+ case 1:
+ return finalMethods[0];
+
+ default:
+ throw CreateAmbiguousMatchException(finalMethods, actionName);
+ }
+ }
+
+ internal List<MethodInfo> GetMatchingAliasedMethods(ControllerContext controllerContext, string actionName)
+ {
+ // find all aliased methods which are opting in to this request
+ // to opt in, all attributes defined on the method must return true
+
+ var methods = from methodInfo in AliasedMethods
+ let attrs = ReflectedAttributeCache.GetActionNameSelectorAttributes(methodInfo)
+ where attrs.All(attr => attr.IsValidName(controllerContext, actionName, methodInfo))
+ select methodInfo;
+ return methods.ToList();
+ }
+
+ private static bool IsMethodDecoratedWithAliasingAttribute(MethodInfo methodInfo)
+ {
+ return methodInfo.IsDefined(typeof(ActionNameSelectorAttribute), true /* inherit */);
+ }
+
+ private static bool IsValidActionMethod(MethodInfo methodInfo)
+ {
+ return !(methodInfo.IsSpecialName ||
+ methodInfo.GetBaseDefinition().DeclaringType.IsAssignableFrom(typeof(Controller)));
+ }
+
+ private void PopulateLookupTables()
+ {
+ MethodInfo[] allMethods = ControllerType.GetMethods(BindingFlags.InvokeMethod | BindingFlags.Instance | BindingFlags.Public);
+ MethodInfo[] actionMethods = Array.FindAll(allMethods, IsValidActionMethod);
+
+ AliasedMethods = Array.FindAll(actionMethods, IsMethodDecoratedWithAliasingAttribute);
+ NonAliasedMethods = actionMethods.Except(AliasedMethods).ToLookup(method => method.Name, StringComparer.OrdinalIgnoreCase);
+ }
+
+ private static List<MethodInfo> RunSelectionFilters(ControllerContext controllerContext, List<MethodInfo> methodInfos)
+ {
+ // remove all methods which are opting out of this request
+ // to opt out, at least one attribute defined on the method must return false
+
+ List<MethodInfo> matchesWithSelectionAttributes = new List<MethodInfo>();
+ List<MethodInfo> matchesWithoutSelectionAttributes = new List<MethodInfo>();
+
+ foreach (MethodInfo methodInfo in methodInfos)
+ {
+ ICollection<ActionMethodSelectorAttribute> attrs = ReflectedAttributeCache.GetActionMethodSelectorAttributes(methodInfo);
+ if (attrs.Count == 0)
+ {
+ matchesWithoutSelectionAttributes.Add(methodInfo);
+ }
+ else if (attrs.All(attr => attr.IsValidForRequest(controllerContext, methodInfo)))
+ {
+ matchesWithSelectionAttributes.Add(methodInfo);
+ }
+ }
+
+ // if a matching action method had a selection attribute, consider it more specific than a matching action method
+ // without a selection attribute
+ return (matchesWithSelectionAttributes.Count > 0) ? matchesWithSelectionAttributes : matchesWithoutSelectionAttributes;
+ }
+ }
+}
diff --git a/src/System.Web.Mvc/ActionMethodSelectorAttribute.cs b/src/System.Web.Mvc/ActionMethodSelectorAttribute.cs
new file mode 100644
index 00000000..9dc3c8c2
--- /dev/null
+++ b/src/System.Web.Mvc/ActionMethodSelectorAttribute.cs
@@ -0,0 +1,10 @@
+using System.Reflection;
+
+namespace System.Web.Mvc
+{
+ [AttributeUsage(AttributeTargets.Method, AllowMultiple = false, Inherited = true)]
+ public abstract class ActionMethodSelectorAttribute : Attribute
+ {
+ public abstract bool IsValidForRequest(ControllerContext controllerContext, MethodInfo methodInfo);
+ }
+}
diff --git a/src/System.Web.Mvc/ActionNameAttribute.cs b/src/System.Web.Mvc/ActionNameAttribute.cs
new file mode 100644
index 00000000..087ef06e
--- /dev/null
+++ b/src/System.Web.Mvc/ActionNameAttribute.cs
@@ -0,0 +1,26 @@
+using System.Reflection;
+using System.Web.Mvc.Properties;
+
+namespace System.Web.Mvc
+{
+ [AttributeUsage(AttributeTargets.Method, AllowMultiple = false, Inherited = true)]
+ public sealed class ActionNameAttribute : ActionNameSelectorAttribute
+ {
+ public ActionNameAttribute(string name)
+ {
+ if (String.IsNullOrEmpty(name))
+ {
+ throw new ArgumentException(MvcResources.Common_NullOrEmpty, "name");
+ }
+
+ Name = name;
+ }
+
+ public string Name { get; private set; }
+
+ public override bool IsValidName(ControllerContext controllerContext, string actionName, MethodInfo methodInfo)
+ {
+ return String.Equals(actionName, Name, StringComparison.OrdinalIgnoreCase);
+ }
+ }
+}
diff --git a/src/System.Web.Mvc/ActionNameSelectorAttribute.cs b/src/System.Web.Mvc/ActionNameSelectorAttribute.cs
new file mode 100644
index 00000000..8b2952fc
--- /dev/null
+++ b/src/System.Web.Mvc/ActionNameSelectorAttribute.cs
@@ -0,0 +1,10 @@
+using System.Reflection;
+
+namespace System.Web.Mvc
+{
+ [AttributeUsage(AttributeTargets.Method, AllowMultiple = false, Inherited = true)]
+ public abstract class ActionNameSelectorAttribute : Attribute
+ {
+ public abstract bool IsValidName(ControllerContext controllerContext, string actionName, MethodInfo methodInfo);
+ }
+}
diff --git a/src/System.Web.Mvc/ActionResult.cs b/src/System.Web.Mvc/ActionResult.cs
new file mode 100644
index 00000000..2dc190c0
--- /dev/null
+++ b/src/System.Web.Mvc/ActionResult.cs
@@ -0,0 +1,7 @@
+namespace System.Web.Mvc
+{
+ public abstract class ActionResult
+ {
+ public abstract void ExecuteResult(ControllerContext context);
+ }
+}
diff --git a/src/System.Web.Mvc/ActionSelector.cs b/src/System.Web.Mvc/ActionSelector.cs
new file mode 100644
index 00000000..ee790b43
--- /dev/null
+++ b/src/System.Web.Mvc/ActionSelector.cs
@@ -0,0 +1,4 @@
+namespace System.Web.Mvc
+{
+ public delegate bool ActionSelector(ControllerContext controllerContext);
+}
diff --git a/src/System.Web.Mvc/AdditionalMetaDataAttribute.cs b/src/System.Web.Mvc/AdditionalMetaDataAttribute.cs
new file mode 100644
index 00000000..0d131f83
--- /dev/null
+++ b/src/System.Web.Mvc/AdditionalMetaDataAttribute.cs
@@ -0,0 +1,38 @@
+namespace System.Web.Mvc
+{
+ [AttributeUsage(AttributeTargets.Class | AttributeTargets.Interface | AttributeTargets.Property, AllowMultiple = true)]
+ public sealed class AdditionalMetadataAttribute : Attribute, IMetadataAware
+ {
+ private object _typeId = new object();
+
+ public AdditionalMetadataAttribute(string name, object value)
+ {
+ if (name == null)
+ {
+ throw new ArgumentNullException("name");
+ }
+
+ Name = name;
+ Value = value;
+ }
+
+ public override object TypeId
+ {
+ get { return _typeId; }
+ }
+
+ public string Name { get; private set; }
+
+ public object Value { get; private set; }
+
+ public void OnMetadataCreated(ModelMetadata metadata)
+ {
+ if (metadata == null)
+ {
+ throw new ArgumentNullException("metadata");
+ }
+
+ metadata.AdditionalValues[Name] = Value;
+ }
+ }
+}
diff --git a/src/System.Web.Mvc/Ajax/AjaxExtensions.cs b/src/System.Web.Mvc/Ajax/AjaxExtensions.cs
new file mode 100644
index 00000000..d2889fa9
--- /dev/null
+++ b/src/System.Web.Mvc/Ajax/AjaxExtensions.cs
@@ -0,0 +1,358 @@
+using System.Collections.Generic;
+using System.Diagnostics.CodeAnalysis;
+using System.Globalization;
+using System.Web.Mvc.Html;
+using System.Web.Mvc.Properties;
+using System.Web.Routing;
+
+namespace System.Web.Mvc.Ajax
+{
+ public static class AjaxExtensions
+ {
+ private const string LinkOnClickFormat = "Sys.Mvc.AsyncHyperlink.handleClick(this, new Sys.UI.DomEvent(event), {0});";
+ private const string FormOnClickValue = "Sys.Mvc.AsyncForm.handleClick(this, new Sys.UI.DomEvent(event));";
+ private const string FormOnSubmitFormat = "Sys.Mvc.AsyncForm.handleSubmit(this, new Sys.UI.DomEvent(event), {0});";
+
+ public static MvcHtmlString ActionLink(this AjaxHelper ajaxHelper, string linkText, string actionName, AjaxOptions ajaxOptions)
+ {
+ return ActionLink(ajaxHelper, linkText, actionName, (string)null /* controllerName */, ajaxOptions);
+ }
+
+ public static MvcHtmlString ActionLink(this AjaxHelper ajaxHelper, string linkText, string actionName, object routeValues, AjaxOptions ajaxOptions)
+ {
+ return ActionLink(ajaxHelper, linkText, actionName, (string)null /* controllerName */, routeValues, ajaxOptions);
+ }
+
+ public static MvcHtmlString ActionLink(this AjaxHelper ajaxHelper, string linkText, string actionName, object routeValues, AjaxOptions ajaxOptions, object htmlAttributes)
+ {
+ return ActionLink(ajaxHelper, linkText, actionName, (string)null /* controllerName */, routeValues, ajaxOptions, htmlAttributes);
+ }
+
+ public static MvcHtmlString ActionLink(this AjaxHelper ajaxHelper, string linkText, string actionName, RouteValueDictionary routeValues, AjaxOptions ajaxOptions)
+ {
+ return ActionLink(ajaxHelper, linkText, actionName, (string)null /* controllerName */, routeValues, ajaxOptions);
+ }
+
+ public static MvcHtmlString ActionLink(this AjaxHelper ajaxHelper, string linkText, string actionName, RouteValueDictionary routeValues, AjaxOptions ajaxOptions, IDictionary<string, object> htmlAttributes)
+ {
+ return ActionLink(ajaxHelper, linkText, actionName, (string)null /* controllerName */, routeValues, ajaxOptions, htmlAttributes);
+ }
+
+ public static MvcHtmlString ActionLink(this AjaxHelper ajaxHelper, string linkText, string actionName, string controllerName, AjaxOptions ajaxOptions)
+ {
+ return ActionLink(ajaxHelper, linkText, actionName, controllerName, null /* values */, ajaxOptions, null /* htmlAttributes */);
+ }
+
+ public static MvcHtmlString ActionLink(this AjaxHelper ajaxHelper, string linkText, string actionName, string controllerName, object routeValues, AjaxOptions ajaxOptions)
+ {
+ return ActionLink(ajaxHelper, linkText, actionName, controllerName, routeValues, ajaxOptions, null /* htmlAttributes */);
+ }
+
+ public static MvcHtmlString ActionLink(this AjaxHelper ajaxHelper, string linkText, string actionName, string controllerName, object routeValues, AjaxOptions ajaxOptions, object htmlAttributes)
+ {
+ RouteValueDictionary newValues = new RouteValueDictionary(routeValues);
+ RouteValueDictionary newAttributes = HtmlHelper.AnonymousObjectToHtmlAttributes(htmlAttributes);
+ return ActionLink(ajaxHelper, linkText, actionName, controllerName, newValues, ajaxOptions, newAttributes);
+ }
+
+ public static MvcHtmlString ActionLink(this AjaxHelper ajaxHelper, string linkText, string actionName, string controllerName, RouteValueDictionary routeValues, AjaxOptions ajaxOptions)
+ {
+ return ActionLink(ajaxHelper, linkText, actionName, controllerName, routeValues, ajaxOptions, null /* htmlAttributes */);
+ }
+
+ public static MvcHtmlString ActionLink(this AjaxHelper ajaxHelper, string linkText, string actionName, string controllerName, RouteValueDictionary routeValues, AjaxOptions ajaxOptions, IDictionary<string, object> htmlAttributes)
+ {
+ if (String.IsNullOrEmpty(linkText))
+ {
+ throw new ArgumentException(MvcResources.Common_NullOrEmpty, "linkText");
+ }
+
+ string targetUrl = UrlHelper.GenerateUrl(null, actionName, controllerName, routeValues, ajaxHelper.RouteCollection, ajaxHelper.ViewContext.RequestContext, true /* includeImplicitMvcValues */);
+
+ return MvcHtmlString.Create(GenerateLink(ajaxHelper, linkText, targetUrl, GetAjaxOptions(ajaxOptions), htmlAttributes));
+ }
+
+ public static MvcHtmlString ActionLink(this AjaxHelper ajaxHelper, string linkText, string actionName, string controllerName, string protocol, string hostName, string fragment, object routeValues, AjaxOptions ajaxOptions, object htmlAttributes)
+ {
+ RouteValueDictionary newValues = new RouteValueDictionary(routeValues);
+ RouteValueDictionary newAttributes = HtmlHelper.AnonymousObjectToHtmlAttributes(htmlAttributes);
+ return ActionLink(ajaxHelper, linkText, actionName, controllerName, protocol, hostName, fragment, newValues, ajaxOptions, newAttributes);
+ }
+
+ public static MvcHtmlString ActionLink(this AjaxHelper ajaxHelper, string linkText, string actionName, string controllerName, string protocol, string hostName, string fragment, RouteValueDictionary routeValues, AjaxOptions ajaxOptions, IDictionary<string, object> htmlAttributes)
+ {
+ if (String.IsNullOrEmpty(linkText))
+ {
+ throw new ArgumentException(MvcResources.Common_NullOrEmpty, "linkText");
+ }
+
+ string targetUrl = UrlHelper.GenerateUrl(null /* routeName */, actionName, controllerName, protocol, hostName, fragment, routeValues, ajaxHelper.RouteCollection, ajaxHelper.ViewContext.RequestContext, true /* includeImplicitMvcValues */);
+
+ return MvcHtmlString.Create(GenerateLink(ajaxHelper, linkText, targetUrl, ajaxOptions, htmlAttributes));
+ }
+
+ public static MvcForm BeginForm(this AjaxHelper ajaxHelper, AjaxOptions ajaxOptions)
+ {
+ string formAction = ajaxHelper.ViewContext.HttpContext.Request.RawUrl;
+ return FormHelper(ajaxHelper, formAction, ajaxOptions, new RouteValueDictionary());
+ }
+
+ public static MvcForm BeginForm(this AjaxHelper ajaxHelper, string actionName, AjaxOptions ajaxOptions)
+ {
+ return BeginForm(ajaxHelper, actionName, (string)null /* controllerName */, ajaxOptions);
+ }
+
+ public static MvcForm BeginForm(this AjaxHelper ajaxHelper, string actionName, object routeValues, AjaxOptions ajaxOptions)
+ {
+ return BeginForm(ajaxHelper, actionName, (string)null /* controllerName */, routeValues, ajaxOptions);
+ }
+
+ public static MvcForm BeginForm(this AjaxHelper ajaxHelper, string actionName, object routeValues, AjaxOptions ajaxOptions, object htmlAttributes)
+ {
+ return BeginForm(ajaxHelper, actionName, (string)null /* controllerName */, routeValues, ajaxOptions, htmlAttributes);
+ }
+
+ public static MvcForm BeginForm(this AjaxHelper ajaxHelper, string actionName, RouteValueDictionary routeValues, AjaxOptions ajaxOptions)
+ {
+ return BeginForm(ajaxHelper, actionName, (string)null /* controllerName */, routeValues, ajaxOptions);
+ }
+
+ public static MvcForm BeginForm(this AjaxHelper ajaxHelper, string actionName, RouteValueDictionary routeValues, AjaxOptions ajaxOptions, IDictionary<string, object> htmlAttributes)
+ {
+ return BeginForm(ajaxHelper, actionName, (string)null /* controllerName */, routeValues, ajaxOptions, htmlAttributes);
+ }
+
+ public static MvcForm BeginForm(this AjaxHelper ajaxHelper, string actionName, string controllerName, AjaxOptions ajaxOptions)
+ {
+ return BeginForm(ajaxHelper, actionName, controllerName, null /* values */, ajaxOptions, null /* htmlAttributes */);
+ }
+
+ public static MvcForm BeginForm(this AjaxHelper ajaxHelper, string actionName, string controllerName, object routeValues, AjaxOptions ajaxOptions)
+ {
+ return BeginForm(ajaxHelper, actionName, controllerName, routeValues, ajaxOptions, null /* htmlAttributes */);
+ }
+
+ public static MvcForm BeginForm(this AjaxHelper ajaxHelper, string actionName, string controllerName, object routeValues, AjaxOptions ajaxOptions, object htmlAttributes)
+ {
+ RouteValueDictionary newValues = new RouteValueDictionary(routeValues);
+ RouteValueDictionary newAttributes = HtmlHelper.AnonymousObjectToHtmlAttributes(htmlAttributes);
+ return BeginForm(ajaxHelper, actionName, controllerName, newValues, ajaxOptions, newAttributes);
+ }
+
+ public static MvcForm BeginForm(this AjaxHelper ajaxHelper, string actionName, string controllerName, RouteValueDictionary routeValues, AjaxOptions ajaxOptions)
+ {
+ return BeginForm(ajaxHelper, actionName, controllerName, routeValues, ajaxOptions, null /* htmlAttributes */);
+ }
+
+ public static MvcForm BeginForm(this AjaxHelper ajaxHelper, string actionName, string controllerName, RouteValueDictionary routeValues, AjaxOptions ajaxOptions, IDictionary<string, object> htmlAttributes)
+ {
+ // get target URL
+ string formAction = UrlHelper.GenerateUrl(null, actionName, controllerName, routeValues ?? new RouteValueDictionary(), ajaxHelper.RouteCollection, ajaxHelper.ViewContext.RequestContext, true /* includeImplicitMvcValues */);
+ return FormHelper(ajaxHelper, formAction, ajaxOptions, htmlAttributes);
+ }
+
+ public static MvcForm BeginRouteForm(this AjaxHelper ajaxHelper, string routeName, AjaxOptions ajaxOptions)
+ {
+ return BeginRouteForm(ajaxHelper, routeName, null /* routeValues */, ajaxOptions, null /* htmlAttributes */);
+ }
+
+ public static MvcForm BeginRouteForm(this AjaxHelper ajaxHelper, string routeName, object routeValues, AjaxOptions ajaxOptions)
+ {
+ return BeginRouteForm(ajaxHelper, routeName, (object)routeValues, ajaxOptions, null /* htmlAttributes */);
+ }
+
+ public static MvcForm BeginRouteForm(this AjaxHelper ajaxHelper, string routeName, object routeValues, AjaxOptions ajaxOptions, object htmlAttributes)
+ {
+ RouteValueDictionary newAttributes = HtmlHelper.AnonymousObjectToHtmlAttributes(htmlAttributes);
+ return BeginRouteForm(ajaxHelper, routeName, new RouteValueDictionary(routeValues), ajaxOptions, newAttributes);
+ }
+
+ public static MvcForm BeginRouteForm(this AjaxHelper ajaxHelper, string routeName, RouteValueDictionary routeValues, AjaxOptions ajaxOptions)
+ {
+ return BeginRouteForm(ajaxHelper, routeName, routeValues, ajaxOptions, null /* htmlAttributes */);
+ }
+
+ public static MvcForm BeginRouteForm(this AjaxHelper ajaxHelper, string routeName, RouteValueDictionary routeValues, AjaxOptions ajaxOptions, IDictionary<string, object> htmlAttributes)
+ {
+ string formAction = UrlHelper.GenerateUrl(routeName, null /* actionName */, null /* controllerName */, routeValues ?? new RouteValueDictionary(), ajaxHelper.RouteCollection, ajaxHelper.ViewContext.RequestContext, false /* includeImplicitMvcValues */);
+ return FormHelper(ajaxHelper, formAction, ajaxOptions, htmlAttributes);
+ }
+
+ [SuppressMessage("Microsoft.Reliability", "CA2000:Dispose objects before losing scope", Justification = "You don't want to dispose of this object unless you intend to write to the response")]
+ private static MvcForm FormHelper(this AjaxHelper ajaxHelper, string formAction, AjaxOptions ajaxOptions, IDictionary<string, object> htmlAttributes)
+ {
+ TagBuilder builder = new TagBuilder("form");
+ builder.MergeAttributes(htmlAttributes);
+ builder.MergeAttribute("action", formAction);
+ builder.MergeAttribute("method", "post");
+
+ ajaxOptions = GetAjaxOptions(ajaxOptions);
+
+ if (ajaxHelper.ViewContext.UnobtrusiveJavaScriptEnabled)
+ {
+ builder.MergeAttributes(ajaxOptions.ToUnobtrusiveHtmlAttributes());
+ }
+ else
+ {
+ builder.MergeAttribute("onclick", FormOnClickValue);
+ builder.MergeAttribute("onsubmit", GenerateAjaxScript(ajaxOptions, FormOnSubmitFormat));
+ }
+
+ if (ajaxHelper.ViewContext.ClientValidationEnabled)
+ {
+ // forms must have an ID for client validation
+ builder.GenerateId(ajaxHelper.ViewContext.FormIdGenerator());
+ }
+
+ ajaxHelper.ViewContext.Writer.Write(builder.ToString(TagRenderMode.StartTag));
+ MvcForm theForm = new MvcForm(ajaxHelper.ViewContext);
+
+ if (ajaxHelper.ViewContext.ClientValidationEnabled)
+ {
+ ajaxHelper.ViewContext.FormContext.FormId = builder.Attributes["id"];
+ }
+
+ return theForm;
+ }
+
+ public static MvcHtmlString GlobalizationScript(this AjaxHelper ajaxHelper)
+ {
+ return GlobalizationScript(ajaxHelper, CultureInfo.CurrentCulture);
+ }
+
+ [SuppressMessage("Microsoft.Usage", "CA1801:ReviewUnusedParameters", MessageId = "ajaxHelper", Justification = "This is an extension method")]
+ public static MvcHtmlString GlobalizationScript(this AjaxHelper ajaxHelper, CultureInfo cultureInfo)
+ {
+ return GlobalizationScriptHelper(AjaxHelper.GlobalizationScriptPath, cultureInfo);
+ }
+
+ internal static MvcHtmlString GlobalizationScriptHelper(string scriptPath, CultureInfo cultureInfo)
+ {
+ if (cultureInfo == null)
+ {
+ throw new ArgumentNullException("cultureInfo");
+ }
+
+ TagBuilder tagBuilder = new TagBuilder("script");
+ tagBuilder.MergeAttribute("type", "text/javascript");
+
+ string src = VirtualPathUtility.AppendTrailingSlash(scriptPath) + HttpUtility.UrlEncode(cultureInfo.Name) + ".js";
+ tagBuilder.MergeAttribute("src", src);
+
+ return tagBuilder.ToMvcHtmlString(TagRenderMode.Normal);
+ }
+
+ public static MvcHtmlString RouteLink(this AjaxHelper ajaxHelper, string linkText, object routeValues, AjaxOptions ajaxOptions)
+ {
+ return RouteLink(ajaxHelper, linkText, null /* routeName */, new RouteValueDictionary(routeValues), ajaxOptions,
+ new Dictionary<string, object>());
+ }
+
+ public static MvcHtmlString RouteLink(this AjaxHelper ajaxHelper, string linkText, object routeValues, AjaxOptions ajaxOptions, object htmlAttributes)
+ {
+ return RouteLink(ajaxHelper, linkText, null /* routeName */, new RouteValueDictionary(routeValues), ajaxOptions,
+ HtmlHelper.AnonymousObjectToHtmlAttributes(htmlAttributes));
+ }
+
+ public static MvcHtmlString RouteLink(this AjaxHelper ajaxHelper, string linkText, RouteValueDictionary routeValues, AjaxOptions ajaxOptions)
+ {
+ return RouteLink(ajaxHelper, linkText, null /* routeName */, routeValues, ajaxOptions,
+ new Dictionary<string, object>());
+ }
+
+ public static MvcHtmlString RouteLink(this AjaxHelper ajaxHelper, string linkText, RouteValueDictionary routeValues, AjaxOptions ajaxOptions, IDictionary<string, object> htmlAttributes)
+ {
+ return RouteLink(ajaxHelper, linkText, null /* routeName */, routeValues, ajaxOptions, htmlAttributes);
+ }
+
+ public static MvcHtmlString RouteLink(this AjaxHelper ajaxHelper, string linkText, string routeName, AjaxOptions ajaxOptions)
+ {
+ return RouteLink(ajaxHelper, linkText, routeName, new RouteValueDictionary(), ajaxOptions,
+ new Dictionary<string, object>());
+ }
+
+ public static MvcHtmlString RouteLink(this AjaxHelper ajaxHelper, string linkText, string routeName, AjaxOptions ajaxOptions, object htmlAttributes)
+ {
+ return RouteLink(ajaxHelper, linkText, routeName, new RouteValueDictionary(), ajaxOptions, HtmlHelper.AnonymousObjectToHtmlAttributes(htmlAttributes));
+ }
+
+ public static MvcHtmlString RouteLink(this AjaxHelper ajaxHelper, string linkText, string routeName, AjaxOptions ajaxOptions, IDictionary<string, object> htmlAttributes)
+ {
+ return RouteLink(ajaxHelper, linkText, routeName, new RouteValueDictionary(), ajaxOptions, htmlAttributes);
+ }
+
+ public static MvcHtmlString RouteLink(this AjaxHelper ajaxHelper, string linkText, string routeName, object routeValues, AjaxOptions ajaxOptions)
+ {
+ return RouteLink(ajaxHelper, linkText, routeName, new RouteValueDictionary(routeValues), ajaxOptions,
+ new Dictionary<string, object>());
+ }
+
+ public static MvcHtmlString RouteLink(this AjaxHelper ajaxHelper, string linkText, string routeName, object routeValues, AjaxOptions ajaxOptions, object htmlAttributes)
+ {
+ return RouteLink(ajaxHelper, linkText, routeName, new RouteValueDictionary(routeValues), ajaxOptions,
+ HtmlHelper.AnonymousObjectToHtmlAttributes(htmlAttributes));
+ }
+
+ public static MvcHtmlString RouteLink(this AjaxHelper ajaxHelper, string linkText, string routeName, RouteValueDictionary routeValues, AjaxOptions ajaxOptions)
+ {
+ return RouteLink(ajaxHelper, linkText, routeName, routeValues, ajaxOptions, new Dictionary<string, object>());
+ }
+
+ public static MvcHtmlString RouteLink(this AjaxHelper ajaxHelper, string linkText, string routeName, RouteValueDictionary routeValues, AjaxOptions ajaxOptions, IDictionary<string, object> htmlAttributes)
+ {
+ if (String.IsNullOrEmpty(linkText))
+ {
+ throw new ArgumentException(MvcResources.Common_NullOrEmpty, "linkText");
+ }
+
+ string targetUrl = UrlHelper.GenerateUrl(routeName, null /* actionName */, null /* controllerName */, routeValues ?? new RouteValueDictionary(), ajaxHelper.RouteCollection, ajaxHelper.ViewContext.RequestContext, false /* includeImplicitMvcValues */);
+
+ return MvcHtmlString.Create(GenerateLink(ajaxHelper, linkText, targetUrl, GetAjaxOptions(ajaxOptions), htmlAttributes));
+ }
+
+ public static MvcHtmlString RouteLink(this AjaxHelper ajaxHelper, string linkText, string routeName, string protocol, string hostName, string fragment, RouteValueDictionary routeValues, AjaxOptions ajaxOptions, IDictionary<string, object> htmlAttributes)
+ {
+ if (String.IsNullOrEmpty(linkText))
+ {
+ throw new ArgumentException(MvcResources.Common_NullOrEmpty, "linkText");
+ }
+
+ string targetUrl = UrlHelper.GenerateUrl(routeName, null /* actionName */, null /* controllerName */, protocol, hostName, fragment, routeValues ?? new RouteValueDictionary(), ajaxHelper.RouteCollection, ajaxHelper.ViewContext.RequestContext, false /* includeImplicitMvcValues */);
+
+ return MvcHtmlString.Create(GenerateLink(ajaxHelper, linkText, targetUrl, GetAjaxOptions(ajaxOptions), htmlAttributes));
+ }
+
+ private static string GenerateLink(AjaxHelper ajaxHelper, string linkText, string targetUrl, AjaxOptions ajaxOptions, IDictionary<string, object> htmlAttributes)
+ {
+ TagBuilder tag = new TagBuilder("a")
+ {
+ InnerHtml = HttpUtility.HtmlEncode(linkText)
+ };
+
+ tag.MergeAttributes(htmlAttributes);
+ tag.MergeAttribute("href", targetUrl);
+
+ if (ajaxHelper.ViewContext.UnobtrusiveJavaScriptEnabled)
+ {
+ tag.MergeAttributes(ajaxOptions.ToUnobtrusiveHtmlAttributes());
+ }
+ else
+ {
+ tag.MergeAttribute("onclick", GenerateAjaxScript(ajaxOptions, LinkOnClickFormat));
+ }
+
+ return tag.ToString(TagRenderMode.Normal);
+ }
+
+ private static string GenerateAjaxScript(AjaxOptions ajaxOptions, string scriptFormat)
+ {
+ string optionsString = ajaxOptions.ToJavascriptString();
+ return String.Format(CultureInfo.InvariantCulture, scriptFormat, optionsString);
+ }
+
+ private static AjaxOptions GetAjaxOptions(AjaxOptions ajaxOptions)
+ {
+ return (ajaxOptions != null) ? ajaxOptions : new AjaxOptions();
+ }
+ }
+}
diff --git a/src/System.Web.Mvc/Ajax/AjaxOptions.cs b/src/System.Web.Mvc/Ajax/AjaxOptions.cs
new file mode 100644
index 00000000..73b8f712
--- /dev/null
+++ b/src/System.Web.Mvc/Ajax/AjaxOptions.cs
@@ -0,0 +1,216 @@
+using System.Collections.Generic;
+using System.Diagnostics.CodeAnalysis;
+using System.Globalization;
+using System.Text;
+
+namespace System.Web.Mvc.Ajax
+{
+ public class AjaxOptions
+ {
+ private string _confirm;
+ private string _httpMethod;
+ private InsertionMode _insertionMode = InsertionMode.Replace;
+ private string _loadingElementId;
+ private string _onBegin;
+ private string _onComplete;
+ private string _onFailure;
+ private string _onSuccess;
+ private string _updateTargetId;
+ private string _url;
+
+ public string Confirm
+ {
+ get { return _confirm ?? String.Empty; }
+ set { _confirm = value; }
+ }
+
+ public string HttpMethod
+ {
+ get { return _httpMethod ?? String.Empty; }
+ set { _httpMethod = value; }
+ }
+
+ public InsertionMode InsertionMode
+ {
+ get { return _insertionMode; }
+ set
+ {
+ switch (value)
+ {
+ case InsertionMode.Replace:
+ case InsertionMode.InsertAfter:
+ case InsertionMode.InsertBefore:
+ _insertionMode = value;
+ return;
+
+ default:
+ throw new ArgumentOutOfRangeException("value");
+ }
+ }
+ }
+
+ internal string InsertionModeString
+ {
+ get
+ {
+ switch (InsertionMode)
+ {
+ case InsertionMode.Replace:
+ return "Sys.Mvc.InsertionMode.replace";
+ case InsertionMode.InsertBefore:
+ return "Sys.Mvc.InsertionMode.insertBefore";
+ case InsertionMode.InsertAfter:
+ return "Sys.Mvc.InsertionMode.insertAfter";
+ default:
+ return ((int)InsertionMode).ToString(CultureInfo.InvariantCulture);
+ }
+ }
+ }
+
+ internal string InsertionModeUnobtrusive
+ {
+ get
+ {
+ switch (InsertionMode)
+ {
+ case InsertionMode.Replace:
+ return "replace";
+ case InsertionMode.InsertBefore:
+ return "before";
+ case InsertionMode.InsertAfter:
+ return "after";
+ default:
+ return ((int)InsertionMode).ToString(CultureInfo.InvariantCulture);
+ }
+ }
+ }
+
+ public int LoadingElementDuration { get; set; }
+
+ public string LoadingElementId
+ {
+ get { return _loadingElementId ?? String.Empty; }
+ set { _loadingElementId = value; }
+ }
+
+ public string OnBegin
+ {
+ get { return _onBegin ?? String.Empty; }
+ set { _onBegin = value; }
+ }
+
+ public string OnComplete
+ {
+ get { return _onComplete ?? String.Empty; }
+ set { _onComplete = value; }
+ }
+
+ public string OnFailure
+ {
+ get { return _onFailure ?? String.Empty; }
+ set { _onFailure = value; }
+ }
+
+ public string OnSuccess
+ {
+ get { return _onSuccess ?? String.Empty; }
+ set { _onSuccess = value; }
+ }
+
+ public string UpdateTargetId
+ {
+ get { return _updateTargetId ?? String.Empty; }
+ set { _updateTargetId = value; }
+ }
+
+ [SuppressMessage("Microsoft.Design", "CA1056:UriPropertiesShouldNotBeStrings", Justification = "This property is used by the optionsBuilder which always accepts a string.")]
+ public string Url
+ {
+ get { return _url ?? String.Empty; }
+ set { _url = value; }
+ }
+
+ internal string ToJavascriptString()
+ {
+ // creates a string of the form { key1: value1, key2 : value2, ... }
+ StringBuilder optionsBuilder = new StringBuilder("{");
+ optionsBuilder.Append(String.Format(CultureInfo.InvariantCulture, " insertionMode: {0},", InsertionModeString));
+ optionsBuilder.Append(PropertyStringIfSpecified("confirm", Confirm));
+ optionsBuilder.Append(PropertyStringIfSpecified("httpMethod", HttpMethod));
+ optionsBuilder.Append(PropertyStringIfSpecified("loadingElementId", LoadingElementId));
+ optionsBuilder.Append(PropertyStringIfSpecified("updateTargetId", UpdateTargetId));
+ optionsBuilder.Append(PropertyStringIfSpecified("url", Url));
+ optionsBuilder.Append(EventStringIfSpecified("onBegin", OnBegin));
+ optionsBuilder.Append(EventStringIfSpecified("onComplete", OnComplete));
+ optionsBuilder.Append(EventStringIfSpecified("onFailure", OnFailure));
+ optionsBuilder.Append(EventStringIfSpecified("onSuccess", OnSuccess));
+ optionsBuilder.Length--;
+ optionsBuilder.Append(" }");
+ return optionsBuilder.ToString();
+ }
+
+ public IDictionary<string, object> ToUnobtrusiveHtmlAttributes()
+ {
+ var result = new Dictionary<string, object>
+ {
+ { "data-ajax", "true" },
+ };
+
+ AddToDictionaryIfSpecified(result, "data-ajax-url", Url);
+ AddToDictionaryIfSpecified(result, "data-ajax-method", HttpMethod);
+ AddToDictionaryIfSpecified(result, "data-ajax-confirm", Confirm);
+
+ AddToDictionaryIfSpecified(result, "data-ajax-begin", OnBegin);
+ AddToDictionaryIfSpecified(result, "data-ajax-complete", OnComplete);
+ AddToDictionaryIfSpecified(result, "data-ajax-failure", OnFailure);
+ AddToDictionaryIfSpecified(result, "data-ajax-success", OnSuccess);
+
+ if (!String.IsNullOrWhiteSpace(LoadingElementId))
+ {
+ result.Add("data-ajax-loading", "#" + LoadingElementId);
+
+ if (LoadingElementDuration > 0)
+ {
+ result.Add("data-ajax-loading-duration", LoadingElementDuration);
+ }
+ }
+
+ if (!String.IsNullOrWhiteSpace(UpdateTargetId))
+ {
+ result.Add("data-ajax-update", "#" + UpdateTargetId);
+ result.Add("data-ajax-mode", InsertionModeUnobtrusive);
+ }
+
+ return result;
+ }
+
+ // Helpers
+
+ private static void AddToDictionaryIfSpecified(IDictionary<string, object> dictionary, string name, string value)
+ {
+ if (!String.IsNullOrWhiteSpace(value))
+ {
+ dictionary.Add(name, value);
+ }
+ }
+
+ private static string EventStringIfSpecified(string propertyName, string handler)
+ {
+ if (!String.IsNullOrEmpty(handler))
+ {
+ return String.Format(CultureInfo.InvariantCulture, " {0}: Function.createDelegate(this, {1}),", propertyName, handler.ToString());
+ }
+ return String.Empty;
+ }
+
+ private static string PropertyStringIfSpecified(string propertyName, string propertyValue)
+ {
+ if (!String.IsNullOrEmpty(propertyValue))
+ {
+ string escapedPropertyValue = propertyValue.Replace("'", @"\'");
+ return String.Format(CultureInfo.InvariantCulture, " {0}: '{1}',", propertyName, escapedPropertyValue);
+ }
+ return String.Empty;
+ }
+ }
+}
diff --git a/src/System.Web.Mvc/Ajax/InsertionMode.cs b/src/System.Web.Mvc/Ajax/InsertionMode.cs
new file mode 100644
index 00000000..3e0affc9
--- /dev/null
+++ b/src/System.Web.Mvc/Ajax/InsertionMode.cs
@@ -0,0 +1,9 @@
+namespace System.Web.Mvc.Ajax
+{
+ public enum InsertionMode
+ {
+ Replace = 0,
+ InsertBefore = 1,
+ InsertAfter = 2
+ }
+}
diff --git a/src/System.Web.Mvc/AjaxHelper.cs b/src/System.Web.Mvc/AjaxHelper.cs
new file mode 100644
index 00000000..ab2d82cb
--- /dev/null
+++ b/src/System.Web.Mvc/AjaxHelper.cs
@@ -0,0 +1,83 @@
+using System.Diagnostics.CodeAnalysis;
+using System.Web.Routing;
+
+namespace System.Web.Mvc
+{
+ public class AjaxHelper
+ {
+ private static string _globalizationScriptPath;
+
+ private DynamicViewDataDictionary _dynamicViewDataDictionary;
+
+ public AjaxHelper(ViewContext viewContext, IViewDataContainer viewDataContainer)
+ : this(viewContext, viewDataContainer, RouteTable.Routes)
+ {
+ }
+
+ public AjaxHelper(ViewContext viewContext, IViewDataContainer viewDataContainer, RouteCollection routeCollection)
+ {
+ if (viewContext == null)
+ {
+ throw new ArgumentNullException("viewContext");
+ }
+ if (viewDataContainer == null)
+ {
+ throw new ArgumentNullException("viewDataContainer");
+ }
+ if (routeCollection == null)
+ {
+ throw new ArgumentNullException("routeCollection");
+ }
+ ViewContext = viewContext;
+ ViewDataContainer = viewDataContainer;
+ RouteCollection = routeCollection;
+ }
+
+ public static string GlobalizationScriptPath
+ {
+ get
+ {
+ if (String.IsNullOrEmpty(_globalizationScriptPath))
+ {
+ _globalizationScriptPath = "~/Scripts/Globalization";
+ }
+ return _globalizationScriptPath;
+ }
+ set { _globalizationScriptPath = value; }
+ }
+
+ public RouteCollection RouteCollection { get; private set; }
+
+ public dynamic ViewBag
+ {
+ get
+ {
+ if (_dynamicViewDataDictionary == null)
+ {
+ _dynamicViewDataDictionary = new DynamicViewDataDictionary(() => ViewData);
+ }
+ return _dynamicViewDataDictionary;
+ }
+ }
+
+ public ViewContext ViewContext { get; private set; }
+
+ public ViewDataDictionary ViewData
+ {
+ get { return ViewDataContainer.ViewData; }
+ }
+
+ public IViewDataContainer ViewDataContainer { get; internal set; }
+
+ [SuppressMessage("Microsoft.Performance", "CA1822:MarkMembersAsStatic", Justification = "Instance method for consistency with other helpers.")]
+ public string JavaScriptStringEncode(string message)
+ {
+ if (String.IsNullOrEmpty(message))
+ {
+ return message;
+ }
+
+ return HttpUtility.JavaScriptStringEncode(message);
+ }
+ }
+}
diff --git a/src/System.Web.Mvc/AjaxHelper`1.cs b/src/System.Web.Mvc/AjaxHelper`1.cs
new file mode 100644
index 00000000..f579b9f1
--- /dev/null
+++ b/src/System.Web.Mvc/AjaxHelper`1.cs
@@ -0,0 +1,39 @@
+using System.Web.Routing;
+
+namespace System.Web.Mvc
+{
+ public class AjaxHelper<TModel> : AjaxHelper
+ {
+ private DynamicViewDataDictionary _dynamicViewDataDictionary;
+ private ViewDataDictionary<TModel> _viewData;
+
+ public AjaxHelper(ViewContext viewContext, IViewDataContainer viewDataContainer)
+ : this(viewContext, viewDataContainer, RouteTable.Routes)
+ {
+ }
+
+ public AjaxHelper(ViewContext viewContext, IViewDataContainer viewDataContainer, RouteCollection routeCollection)
+ : base(viewContext, viewDataContainer, routeCollection)
+ {
+ _viewData = new ViewDataDictionary<TModel>(viewDataContainer.ViewData);
+ }
+
+ public new dynamic ViewBag
+ {
+ get
+ {
+ if (_dynamicViewDataDictionary == null)
+ {
+ _dynamicViewDataDictionary = new DynamicViewDataDictionary(() => ViewData);
+ }
+
+ return _dynamicViewDataDictionary;
+ }
+ }
+
+ public new ViewDataDictionary<TModel> ViewData
+ {
+ get { return _viewData; }
+ }
+ }
+}
diff --git a/src/System.Web.Mvc/AjaxRequestExtensions.cs b/src/System.Web.Mvc/AjaxRequestExtensions.cs
new file mode 100644
index 00000000..e890fdcc
--- /dev/null
+++ b/src/System.Web.Mvc/AjaxRequestExtensions.cs
@@ -0,0 +1,15 @@
+namespace System.Web.Mvc
+{
+ public static class AjaxRequestExtensions
+ {
+ public static bool IsAjaxRequest(this HttpRequestBase request)
+ {
+ if (request == null)
+ {
+ throw new ArgumentNullException("request");
+ }
+
+ return (request["X-Requested-With"] == "XMLHttpRequest") || ((request.Headers != null) && (request.Headers["X-Requested-With"] == "XMLHttpRequest"));
+ }
+ }
+}
diff --git a/src/System.Web.Mvc/AllowAnonymousAttribute.cs b/src/System.Web.Mvc/AllowAnonymousAttribute.cs
new file mode 100644
index 00000000..780e7c51
--- /dev/null
+++ b/src/System.Web.Mvc/AllowAnonymousAttribute.cs
@@ -0,0 +1,11 @@
+namespace System.Web.Mvc
+{
+ /// <summary>
+ /// Actions and controllers with the AllowAnonymous attribute are skipped by the Authorize attribute
+ /// on authorization. See AccountController.cs in the project template for an example.
+ /// </summary>
+ [AttributeUsage(AttributeTargets.Class | AttributeTargets.Method, AllowMultiple = false, Inherited = true)]
+ public sealed class AllowAnonymousAttribute : Attribute
+ {
+ }
+}
diff --git a/src/System.Web.Mvc/AllowHtmlAttribute.cs b/src/System.Web.Mvc/AllowHtmlAttribute.cs
new file mode 100644
index 00000000..66abb499
--- /dev/null
+++ b/src/System.Web.Mvc/AllowHtmlAttribute.cs
@@ -0,0 +1,19 @@
+namespace System.Web.Mvc
+{
+ // This attribute can be applied to a model property to specify that the particular property to
+ // which it is applied should not go through request validation.
+
+ [AttributeUsage(AttributeTargets.Property, AllowMultiple = false, Inherited = true)]
+ public sealed class AllowHtmlAttribute : Attribute, IMetadataAware
+ {
+ public void OnMetadataCreated(ModelMetadata metadata)
+ {
+ if (metadata == null)
+ {
+ throw new ArgumentNullException("metadata");
+ }
+
+ metadata.RequestValidationEnabled = false;
+ }
+ }
+}
diff --git a/src/System.Web.Mvc/AreaHelpers.cs b/src/System.Web.Mvc/AreaHelpers.cs
new file mode 100644
index 00000000..e8deaed0
--- /dev/null
+++ b/src/System.Web.Mvc/AreaHelpers.cs
@@ -0,0 +1,35 @@
+using System.Web.Routing;
+
+namespace System.Web.Mvc
+{
+ internal static class AreaHelpers
+ {
+ public static string GetAreaName(RouteBase route)
+ {
+ IRouteWithArea routeWithArea = route as IRouteWithArea;
+ if (routeWithArea != null)
+ {
+ return routeWithArea.Area;
+ }
+
+ Route castRoute = route as Route;
+ if (castRoute != null && castRoute.DataTokens != null)
+ {
+ return castRoute.DataTokens["area"] as string;
+ }
+
+ return null;
+ }
+
+ public static string GetAreaName(RouteData routeData)
+ {
+ object area;
+ if (routeData.DataTokens.TryGetValue("area", out area))
+ {
+ return area as string;
+ }
+
+ return GetAreaName(routeData.Route);
+ }
+ }
+}
diff --git a/src/System.Web.Mvc/AreaRegistration.cs b/src/System.Web.Mvc/AreaRegistration.cs
new file mode 100644
index 00000000..6b80fe7e
--- /dev/null
+++ b/src/System.Web.Mvc/AreaRegistration.cs
@@ -0,0 +1,54 @@
+using System.Collections.Generic;
+using System.Web.Routing;
+
+namespace System.Web.Mvc
+{
+ public abstract class AreaRegistration
+ {
+ private const string TypeCacheName = "MVC-AreaRegistrationTypeCache.xml";
+
+ public abstract string AreaName { get; }
+
+ internal void CreateContextAndRegister(RouteCollection routes, object state)
+ {
+ AreaRegistrationContext context = new AreaRegistrationContext(AreaName, routes, state);
+
+ string thisNamespace = GetType().Namespace;
+ if (thisNamespace != null)
+ {
+ context.Namespaces.Add(thisNamespace + ".*");
+ }
+
+ RegisterArea(context);
+ }
+
+ private static bool IsAreaRegistrationType(Type type)
+ {
+ return
+ typeof(AreaRegistration).IsAssignableFrom(type) &&
+ type.GetConstructor(Type.EmptyTypes) != null;
+ }
+
+ public static void RegisterAllAreas()
+ {
+ RegisterAllAreas(null);
+ }
+
+ public static void RegisterAllAreas(object state)
+ {
+ RegisterAllAreas(RouteTable.Routes, new BuildManagerWrapper(), state);
+ }
+
+ internal static void RegisterAllAreas(RouteCollection routes, IBuildManager buildManager, object state)
+ {
+ List<Type> areaRegistrationTypes = TypeCacheUtil.GetFilteredTypesFromAssemblies(TypeCacheName, IsAreaRegistrationType, buildManager);
+ foreach (Type areaRegistrationType in areaRegistrationTypes)
+ {
+ AreaRegistration registration = (AreaRegistration)Activator.CreateInstance(areaRegistrationType);
+ registration.CreateContextAndRegister(routes, state);
+ }
+ }
+
+ public abstract void RegisterArea(AreaRegistrationContext context);
+ }
+}
diff --git a/src/System.Web.Mvc/AreaRegistrationContext.cs b/src/System.Web.Mvc/AreaRegistrationContext.cs
new file mode 100644
index 00000000..44a9a44f
--- /dev/null
+++ b/src/System.Web.Mvc/AreaRegistrationContext.cs
@@ -0,0 +1,93 @@
+using System.Collections.Generic;
+using System.Diagnostics.CodeAnalysis;
+using System.Linq;
+using System.Web.Routing;
+
+namespace System.Web.Mvc
+{
+ public class AreaRegistrationContext
+ {
+ private readonly HashSet<string> _namespaces = new HashSet<string>(StringComparer.OrdinalIgnoreCase);
+
+ public AreaRegistrationContext(string areaName, RouteCollection routes)
+ : this(areaName, routes, null)
+ {
+ }
+
+ public AreaRegistrationContext(string areaName, RouteCollection routes, object state)
+ {
+ if (String.IsNullOrEmpty(areaName))
+ {
+ throw Error.ParameterCannotBeNullOrEmpty("areaName");
+ }
+ if (routes == null)
+ {
+ throw new ArgumentNullException("routes");
+ }
+
+ AreaName = areaName;
+ Routes = routes;
+ State = state;
+ }
+
+ public string AreaName { get; private set; }
+
+ public ICollection<string> Namespaces
+ {
+ get { return _namespaces; }
+ }
+
+ public RouteCollection Routes { get; private set; }
+
+ public object State { get; private set; }
+
+ [SuppressMessage("Microsoft.Design", "CA1054:UriParametersShouldNotBeStrings", MessageId = "1#", Justification = "This is not a regular URL as it may contain special routing characters.")]
+ public Route MapRoute(string name, string url)
+ {
+ return MapRoute(name, url, (object)null /* defaults */);
+ }
+
+ [SuppressMessage("Microsoft.Design", "CA1054:UriParametersShouldNotBeStrings", MessageId = "1#", Justification = "This is not a regular URL as it may contain special routing characters.")]
+ public Route MapRoute(string name, string url, object defaults)
+ {
+ return MapRoute(name, url, defaults, (object)null /* constraints */);
+ }
+
+ [SuppressMessage("Microsoft.Design", "CA1054:UriParametersShouldNotBeStrings", MessageId = "1#", Justification = "This is not a regular URL as it may contain special routing characters.")]
+ public Route MapRoute(string name, string url, object defaults, object constraints)
+ {
+ return MapRoute(name, url, defaults, constraints, null /* namespaces */);
+ }
+
+ [SuppressMessage("Microsoft.Design", "CA1054:UriParametersShouldNotBeStrings", MessageId = "1#", Justification = "This is not a regular URL as it may contain special routing characters.")]
+ public Route MapRoute(string name, string url, string[] namespaces)
+ {
+ return MapRoute(name, url, (object)null /* defaults */, namespaces);
+ }
+
+ [SuppressMessage("Microsoft.Design", "CA1054:UriParametersShouldNotBeStrings", MessageId = "1#", Justification = "This is not a regular URL as it may contain special routing characters.")]
+ public Route MapRoute(string name, string url, object defaults, string[] namespaces)
+ {
+ return MapRoute(name, url, defaults, null /* constraints */, namespaces);
+ }
+
+ [SuppressMessage("Microsoft.Design", "CA1054:UriParametersShouldNotBeStrings", MessageId = "1#", Justification = "This is not a regular URL as it may contain special routing characters.")]
+ public Route MapRoute(string name, string url, object defaults, object constraints, string[] namespaces)
+ {
+ if (namespaces == null && Namespaces != null)
+ {
+ namespaces = Namespaces.ToArray();
+ }
+
+ Route route = Routes.MapRoute(name, url, defaults, constraints, namespaces);
+ route.DataTokens["area"] = AreaName;
+
+ // disabling the namespace lookup fallback mechanism keeps this areas from accidentally picking up
+ // controllers belonging to other areas
+ bool useNamespaceFallback = (namespaces == null || namespaces.Length == 0);
+ route.DataTokens["UseNamespaceFallback"] = useNamespaceFallback;
+
+ return route;
+ }
+ }
+}
diff --git a/src/System.Web.Mvc/AssociatedMetadataProvider.cs b/src/System.Web.Mvc/AssociatedMetadataProvider.cs
new file mode 100644
index 00000000..1d1b7617
--- /dev/null
+++ b/src/System.Web.Mvc/AssociatedMetadataProvider.cs
@@ -0,0 +1,110 @@
+using System.Collections.Generic;
+using System.ComponentModel;
+using System.Globalization;
+using System.Linq;
+using System.Web.Mvc.Properties;
+
+namespace System.Web.Mvc
+{
+ // This class provides a good implementation of ModelMetadataProvider for people who will be
+ // using traditional classes with properties. It uses the buddy class support from
+ // DataAnnotations, and consolidates the three operations down to a single override
+ // for reading the attribute values and creating the metadata class.
+ public abstract class AssociatedMetadataProvider : ModelMetadataProvider
+ {
+ private static void ApplyMetadataAwareAttributes(IEnumerable<Attribute> attributes, ModelMetadata result)
+ {
+ foreach (IMetadataAware awareAttribute in attributes.OfType<IMetadataAware>())
+ {
+ awareAttribute.OnMetadataCreated(result);
+ }
+ }
+
+ protected abstract ModelMetadata CreateMetadata(IEnumerable<Attribute> attributes, Type containerType, Func<object> modelAccessor, Type modelType, string propertyName);
+
+ protected virtual IEnumerable<Attribute> FilterAttributes(Type containerType, PropertyDescriptor propertyDescriptor, IEnumerable<Attribute> attributes)
+ {
+ if (typeof(ViewPage).IsAssignableFrom(containerType) || typeof(ViewUserControl).IsAssignableFrom(containerType))
+ {
+ return attributes.Where(a => !(a is ReadOnlyAttribute));
+ }
+
+ return attributes;
+ }
+
+ public override IEnumerable<ModelMetadata> GetMetadataForProperties(object container, Type containerType)
+ {
+ if (containerType == null)
+ {
+ throw new ArgumentNullException("containerType");
+ }
+
+ return GetMetadataForPropertiesImpl(container, containerType);
+ }
+
+ private IEnumerable<ModelMetadata> GetMetadataForPropertiesImpl(object container, Type containerType)
+ {
+ foreach (PropertyDescriptor property in GetTypeDescriptor(containerType).GetProperties())
+ {
+ Func<object> modelAccessor = container == null ? null : GetPropertyValueAccessor(container, property);
+ yield return GetMetadataForProperty(modelAccessor, containerType, property);
+ }
+ }
+
+ public override ModelMetadata GetMetadataForProperty(Func<object> modelAccessor, Type containerType, string propertyName)
+ {
+ if (containerType == null)
+ {
+ throw new ArgumentNullException("containerType");
+ }
+ if (String.IsNullOrEmpty(propertyName))
+ {
+ throw new ArgumentException(MvcResources.Common_NullOrEmpty, "propertyName");
+ }
+
+ ICustomTypeDescriptor typeDescriptor = GetTypeDescriptor(containerType);
+ PropertyDescriptor property = typeDescriptor.GetProperties().Find(propertyName, true);
+ if (property == null)
+ {
+ throw new ArgumentException(
+ String.Format(
+ CultureInfo.CurrentCulture,
+ MvcResources.Common_PropertyNotFound,
+ containerType.FullName, propertyName));
+ }
+
+ return GetMetadataForProperty(modelAccessor, containerType, property);
+ }
+
+ protected virtual ModelMetadata GetMetadataForProperty(Func<object> modelAccessor, Type containerType, PropertyDescriptor propertyDescriptor)
+ {
+ IEnumerable<Attribute> attributes = FilterAttributes(containerType, propertyDescriptor, propertyDescriptor.Attributes.Cast<Attribute>());
+ ModelMetadata result = CreateMetadata(attributes, containerType, modelAccessor, propertyDescriptor.PropertyType, propertyDescriptor.Name);
+ ApplyMetadataAwareAttributes(attributes, result);
+ return result;
+ }
+
+ public override ModelMetadata GetMetadataForType(Func<object> modelAccessor, Type modelType)
+ {
+ if (modelType == null)
+ {
+ throw new ArgumentNullException("modelType");
+ }
+
+ IEnumerable<Attribute> attributes = GetTypeDescriptor(modelType).GetAttributes().Cast<Attribute>();
+ ModelMetadata result = CreateMetadata(attributes, null /* containerType */, modelAccessor, modelType, null /* propertyName */);
+ ApplyMetadataAwareAttributes(attributes, result);
+ return result;
+ }
+
+ private static Func<object> GetPropertyValueAccessor(object container, PropertyDescriptor property)
+ {
+ return () => property.GetValue(container);
+ }
+
+ protected virtual ICustomTypeDescriptor GetTypeDescriptor(Type type)
+ {
+ return TypeDescriptorHelper.Get(type);
+ }
+ }
+}
diff --git a/src/System.Web.Mvc/AssociatedValidatorProvider.cs b/src/System.Web.Mvc/AssociatedValidatorProvider.cs
new file mode 100644
index 00000000..b91fade1
--- /dev/null
+++ b/src/System.Web.Mvc/AssociatedValidatorProvider.cs
@@ -0,0 +1,59 @@
+using System.Collections.Generic;
+using System.ComponentModel;
+using System.Globalization;
+using System.Linq;
+using System.Web.Mvc.Properties;
+
+namespace System.Web.Mvc
+{
+ public abstract class AssociatedValidatorProvider : ModelValidatorProvider
+ {
+ protected virtual ICustomTypeDescriptor GetTypeDescriptor(Type type)
+ {
+ return TypeDescriptorHelper.Get(type);
+ }
+
+ public sealed override IEnumerable<ModelValidator> GetValidators(ModelMetadata metadata, ControllerContext context)
+ {
+ if (metadata == null)
+ {
+ throw new ArgumentNullException("metadata");
+ }
+ if (context == null)
+ {
+ throw new ArgumentNullException("context");
+ }
+
+ if (metadata.ContainerType != null && !String.IsNullOrEmpty(metadata.PropertyName))
+ {
+ return GetValidatorsForProperty(metadata, context);
+ }
+
+ return GetValidatorsForType(metadata, context);
+ }
+
+ protected abstract IEnumerable<ModelValidator> GetValidators(ModelMetadata metadata, ControllerContext context, IEnumerable<Attribute> attributes);
+
+ private IEnumerable<ModelValidator> GetValidatorsForProperty(ModelMetadata metadata, ControllerContext context)
+ {
+ ICustomTypeDescriptor typeDescriptor = GetTypeDescriptor(metadata.ContainerType);
+ PropertyDescriptor property = typeDescriptor.GetProperties().Find(metadata.PropertyName, true);
+ if (property == null)
+ {
+ throw new ArgumentException(
+ String.Format(
+ CultureInfo.CurrentCulture,
+ MvcResources.Common_PropertyNotFound,
+ metadata.ContainerType.FullName, metadata.PropertyName),
+ "metadata");
+ }
+
+ return GetValidators(metadata, context, property.Attributes.OfType<Attribute>());
+ }
+
+ private IEnumerable<ModelValidator> GetValidatorsForType(ModelMetadata metadata, ControllerContext context)
+ {
+ return GetValidators(metadata, context, GetTypeDescriptor(metadata.ModelType).GetAttributes().Cast<Attribute>());
+ }
+ }
+}
diff --git a/src/System.Web.Mvc/Async/ActionDescriptorCreator.cs b/src/System.Web.Mvc/Async/ActionDescriptorCreator.cs
new file mode 100644
index 00000000..0ecd2fc1
--- /dev/null
+++ b/src/System.Web.Mvc/Async/ActionDescriptorCreator.cs
@@ -0,0 +1,4 @@
+namespace System.Web.Mvc.Async
+{
+ internal delegate ActionDescriptor ActionDescriptorCreator(string actionName, ControllerDescriptor controllerDescriptor);
+}
diff --git a/src/System.Web.Mvc/Async/AsyncActionDescriptor.cs b/src/System.Web.Mvc/Async/AsyncActionDescriptor.cs
new file mode 100644
index 00000000..8fd7567e
--- /dev/null
+++ b/src/System.Web.Mvc/Async/AsyncActionDescriptor.cs
@@ -0,0 +1,32 @@
+using System.Collections.Generic;
+using System.Globalization;
+using System.Web.Mvc.Properties;
+
+namespace System.Web.Mvc.Async
+{
+ public abstract class AsyncActionDescriptor : ActionDescriptor
+ {
+ public abstract IAsyncResult BeginExecute(ControllerContext controllerContext, IDictionary<string, object> parameters, AsyncCallback callback, object state);
+
+ public abstract object EndExecute(IAsyncResult asyncResult);
+
+ public override object Execute(ControllerContext controllerContext, IDictionary<string, object> parameters)
+ {
+ string errorMessage = String.Format(CultureInfo.CurrentCulture, MvcResources.AsyncActionDescriptor_CannotExecuteSynchronously,
+ ActionName);
+
+ throw new InvalidOperationException(errorMessage);
+ }
+
+ internal static AsyncManager GetAsyncManager(ControllerBase controller)
+ {
+ IAsyncManagerContainer helperContainer = controller as IAsyncManagerContainer;
+ if (helperContainer == null)
+ {
+ throw Error.AsyncCommon_ControllerMustImplementIAsyncManagerContainer(controller.GetType());
+ }
+
+ return helperContainer.AsyncManager;
+ }
+ }
+}
diff --git a/src/System.Web.Mvc/Async/AsyncActionMethodSelector.cs b/src/System.Web.Mvc/Async/AsyncActionMethodSelector.cs
new file mode 100644
index 00000000..0d5d0d55
--- /dev/null
+++ b/src/System.Web.Mvc/Async/AsyncActionMethodSelector.cs
@@ -0,0 +1,227 @@
+using System.Collections.Generic;
+using System.Globalization;
+using System.Linq;
+using System.Reflection;
+using System.Text;
+using System.Threading.Tasks;
+using System.Web.Mvc.Properties;
+
+namespace System.Web.Mvc.Async
+{
+ internal sealed class AsyncActionMethodSelector
+ {
+ // This flag controls async action binding for backwards compat since Controller now supports async.
+ // Set to true for classes that derive from AsyncController. In this case, FooAsync/FooCompleted is
+ // bound as a single async action pair "Foo". If false, they're bound as 2 separate sync actions.
+ // Practically, if this is false, then IsAsyncSuffixedMethod and IsCompeltedSuffixedMethod return false.
+ private bool _allowLegacyAsyncActions;
+
+ public AsyncActionMethodSelector(Type controllerType, bool allowLegacyAsyncActions = true)
+ {
+ _allowLegacyAsyncActions = allowLegacyAsyncActions;
+ ControllerType = controllerType;
+ PopulateLookupTables();
+ }
+
+ public Type ControllerType { get; private set; }
+
+ public MethodInfo[] AliasedMethods { get; private set; }
+
+ public ILookup<string, MethodInfo> NonAliasedMethods { get; private set; }
+
+ private AmbiguousMatchException CreateAmbiguousActionMatchException(IEnumerable<MethodInfo> ambiguousMethods, string actionName)
+ {
+ string ambiguityList = CreateAmbiguousMatchList(ambiguousMethods);
+ string message = String.Format(CultureInfo.CurrentCulture, MvcResources.ActionMethodSelector_AmbiguousMatch,
+ actionName, ControllerType.Name, ambiguityList);
+ return new AmbiguousMatchException(message);
+ }
+
+ private AmbiguousMatchException CreateAmbiguousMethodMatchException(IEnumerable<MethodInfo> ambiguousMethods, string methodName)
+ {
+ string ambiguityList = CreateAmbiguousMatchList(ambiguousMethods);
+ string message = String.Format(CultureInfo.CurrentCulture, MvcResources.AsyncActionMethodSelector_AmbiguousMethodMatch,
+ methodName, ControllerType.Name, ambiguityList);
+ return new AmbiguousMatchException(message);
+ }
+
+ private static string CreateAmbiguousMatchList(IEnumerable<MethodInfo> ambiguousMethods)
+ {
+ StringBuilder exceptionMessageBuilder = new StringBuilder();
+ foreach (MethodInfo methodInfo in ambiguousMethods)
+ {
+ exceptionMessageBuilder.AppendLine();
+ exceptionMessageBuilder.AppendFormat(CultureInfo.CurrentCulture, MvcResources.ActionMethodSelector_AmbiguousMatchType, methodInfo, methodInfo.DeclaringType.FullName);
+ }
+
+ return exceptionMessageBuilder.ToString();
+ }
+
+ public ActionDescriptorCreator FindAction(ControllerContext controllerContext, string actionName)
+ {
+ List<MethodInfo> methodsMatchingName = GetMatchingAliasedMethods(controllerContext, actionName);
+ methodsMatchingName.AddRange(NonAliasedMethods[actionName]);
+ List<MethodInfo> finalMethods = RunSelectionFilters(controllerContext, methodsMatchingName);
+
+ switch (finalMethods.Count)
+ {
+ case 0:
+ return null;
+
+ case 1:
+ MethodInfo entryMethod = finalMethods[0];
+ return GetActionDescriptorDelegate(entryMethod);
+
+ default:
+ throw CreateAmbiguousActionMatchException(finalMethods, actionName);
+ }
+ }
+
+ private ActionDescriptorCreator GetActionDescriptorDelegate(MethodInfo entryMethod)
+ {
+ // Does the action return a Task?
+ if (entryMethod.ReturnType != null && typeof(Task).IsAssignableFrom(entryMethod.ReturnType))
+ {
+ return (actionName, controllerDescriptor) => new TaskAsyncActionDescriptor(entryMethod, actionName, controllerDescriptor);
+ }
+
+ // Is this the FooAsync() / FooCompleted() pattern?
+ if (IsAsyncSuffixedMethod(entryMethod))
+ {
+ string completionMethodName = entryMethod.Name.Substring(0, entryMethod.Name.Length - "Async".Length) + "Completed";
+ MethodInfo completionMethod = GetMethodByName(completionMethodName);
+ if (completionMethod != null)
+ {
+ return (actionName, controllerDescriptor) => new ReflectedAsyncActionDescriptor(entryMethod, completionMethod, actionName, controllerDescriptor);
+ }
+ else
+ {
+ throw Error.AsyncActionMethodSelector_CouldNotFindMethod(completionMethodName, ControllerType);
+ }
+ }
+
+ // Fallback to synchronous method
+ return (actionName, controllerDescriptor) => new ReflectedActionDescriptor(entryMethod, actionName, controllerDescriptor);
+ }
+
+ private string GetCanonicalMethodName(MethodInfo methodInfo)
+ {
+ string methodName = methodInfo.Name;
+ return (IsAsyncSuffixedMethod(methodInfo))
+ ? methodName.Substring(0, methodName.Length - "Async".Length)
+ : methodName;
+ }
+
+ internal List<MethodInfo> GetMatchingAliasedMethods(ControllerContext controllerContext, string actionName)
+ {
+ // find all aliased methods which are opting in to this request
+ // to opt in, all attributes defined on the method must return true
+
+ var methods = from methodInfo in AliasedMethods
+ let attrs = ReflectedAttributeCache.GetActionNameSelectorAttributes(methodInfo)
+ where attrs.All(attr => attr.IsValidName(controllerContext, actionName, methodInfo))
+ select methodInfo;
+ return methods.ToList();
+ }
+
+ private bool IsAsyncSuffixedMethod(MethodInfo methodInfo)
+ {
+ return _allowLegacyAsyncActions && methodInfo.Name.EndsWith("Async", StringComparison.OrdinalIgnoreCase);
+ }
+
+ private bool IsCompletedSuffixedMethod(MethodInfo methodInfo)
+ {
+ return _allowLegacyAsyncActions && methodInfo.Name.EndsWith("Completed", StringComparison.OrdinalIgnoreCase);
+ }
+
+ private static bool IsMethodDecoratedWithAliasingAttribute(MethodInfo methodInfo)
+ {
+ return methodInfo.IsDefined(typeof(ActionNameSelectorAttribute), true /* inherit */);
+ }
+
+ private MethodInfo GetMethodByName(string methodName)
+ {
+ List<MethodInfo> methods = (from MethodInfo methodInfo in ControllerType.GetMember(methodName, MemberTypes.Method, BindingFlags.Instance | BindingFlags.Public | BindingFlags.InvokeMethod | BindingFlags.IgnoreCase)
+ where IsValidActionMethod(methodInfo, false /* stripInfrastructureMethods */)
+ select methodInfo).ToList();
+
+ switch (methods.Count)
+ {
+ case 0:
+ return null;
+
+ case 1:
+ return methods[0];
+
+ default:
+ throw CreateAmbiguousMethodMatchException(methods, methodName);
+ }
+ }
+
+ private bool IsValidActionMethod(MethodInfo methodInfo)
+ {
+ return IsValidActionMethod(methodInfo, true /* stripInfrastructureMethods */);
+ }
+
+ private bool IsValidActionMethod(MethodInfo methodInfo, bool stripInfrastructureMethods)
+ {
+ if (methodInfo.IsSpecialName)
+ {
+ // not a normal method, e.g. a constructor or an event
+ return false;
+ }
+
+ if (methodInfo.GetBaseDefinition().DeclaringType.IsAssignableFrom(typeof(AsyncController)))
+ {
+ // is a method on Object, ControllerBase, Controller, or AsyncController
+ return false;
+ }
+
+ if (stripInfrastructureMethods)
+ {
+ if (IsCompletedSuffixedMethod(methodInfo))
+ {
+ // do not match FooCompleted() methods, as these are infrastructure methods
+ return false;
+ }
+ }
+
+ return true;
+ }
+
+ private void PopulateLookupTables()
+ {
+ MethodInfo[] allMethods = ControllerType.GetMethods(BindingFlags.InvokeMethod | BindingFlags.Instance | BindingFlags.Public);
+ MethodInfo[] actionMethods = Array.FindAll(allMethods, IsValidActionMethod);
+
+ AliasedMethods = Array.FindAll(actionMethods, IsMethodDecoratedWithAliasingAttribute);
+ NonAliasedMethods = actionMethods.Except(AliasedMethods).ToLookup(GetCanonicalMethodName, StringComparer.OrdinalIgnoreCase);
+ }
+
+ private static List<MethodInfo> RunSelectionFilters(ControllerContext controllerContext, List<MethodInfo> methodInfos)
+ {
+ // remove all methods which are opting out of this request
+ // to opt out, at least one attribute defined on the method must return false
+
+ List<MethodInfo> matchesWithSelectionAttributes = new List<MethodInfo>();
+ List<MethodInfo> matchesWithoutSelectionAttributes = new List<MethodInfo>();
+
+ foreach (MethodInfo methodInfo in methodInfos)
+ {
+ ICollection<ActionMethodSelectorAttribute> attrs = ReflectedAttributeCache.GetActionMethodSelectorAttributes(methodInfo);
+ if (attrs.Count == 0)
+ {
+ matchesWithoutSelectionAttributes.Add(methodInfo);
+ }
+ else if (attrs.All(attr => attr.IsValidForRequest(controllerContext, methodInfo)))
+ {
+ matchesWithSelectionAttributes.Add(methodInfo);
+ }
+ }
+
+ // if a matching action method had a selection attribute, consider it more specific than a matching action method
+ // without a selection attribute
+ return (matchesWithSelectionAttributes.Count > 0) ? matchesWithSelectionAttributes : matchesWithoutSelectionAttributes;
+ }
+ }
+}
diff --git a/src/System.Web.Mvc/Async/AsyncControllerActionInvoker.cs b/src/System.Web.Mvc/Async/AsyncControllerActionInvoker.cs
new file mode 100644
index 00000000..0d62065c
--- /dev/null
+++ b/src/System.Web.Mvc/Async/AsyncControllerActionInvoker.cs
@@ -0,0 +1,324 @@
+using System.Collections.Generic;
+using System.Linq;
+using System.Threading;
+
+namespace System.Web.Mvc.Async
+{
+ public class AsyncControllerActionInvoker : ControllerActionInvoker, IAsyncActionInvoker
+ {
+ private static readonly object _invokeActionTag = new object();
+ private static readonly object _invokeActionMethodTag = new object();
+ private static readonly object _invokeActionMethodWithFiltersTag = new object();
+
+ public virtual IAsyncResult BeginInvokeAction(ControllerContext controllerContext, string actionName, AsyncCallback callback, object state)
+ {
+ if (controllerContext == null)
+ {
+ throw new ArgumentNullException("controllerContext");
+ }
+ if (String.IsNullOrEmpty(actionName))
+ {
+ throw Error.ParameterCannotBeNullOrEmpty("actionName");
+ }
+
+ ControllerDescriptor controllerDescriptor = GetControllerDescriptor(controllerContext);
+ ActionDescriptor actionDescriptor = FindAction(controllerContext, controllerDescriptor, actionName);
+ if (actionDescriptor != null)
+ {
+ FilterInfo filterInfo = GetFilters(controllerContext, actionDescriptor);
+ Action continuation = null;
+
+ BeginInvokeDelegate beginDelegate = delegate(AsyncCallback asyncCallback, object asyncState)
+ {
+ try
+ {
+ AuthorizationContext authContext = InvokeAuthorizationFilters(controllerContext, filterInfo.AuthorizationFilters, actionDescriptor);
+ if (authContext.Result != null)
+ {
+ // the auth filter signaled that we should let it short-circuit the request
+ continuation = () => InvokeActionResult(controllerContext, authContext.Result);
+ }
+ else
+ {
+ if (controllerContext.Controller.ValidateRequest)
+ {
+ ValidateRequest(controllerContext);
+ }
+
+ IDictionary<string, object> parameters = GetParameterValues(controllerContext, actionDescriptor);
+ IAsyncResult asyncResult = BeginInvokeActionMethodWithFilters(controllerContext, filterInfo.ActionFilters, actionDescriptor, parameters, asyncCallback, asyncState);
+ continuation = () =>
+ {
+ ActionExecutedContext postActionContext = EndInvokeActionMethodWithFilters(asyncResult);
+ InvokeActionResultWithFilters(controllerContext, filterInfo.ResultFilters, postActionContext.Result);
+ };
+ return asyncResult;
+ }
+ }
+ catch (ThreadAbortException)
+ {
+ // This type of exception occurs as a result of Response.Redirect(), but we special-case so that
+ // the filters don't see this as an error.
+ throw;
+ }
+ catch (Exception ex)
+ {
+ // something blew up, so execute the exception filters
+ ExceptionContext exceptionContext = InvokeExceptionFilters(controllerContext, filterInfo.ExceptionFilters, ex);
+ if (!exceptionContext.ExceptionHandled)
+ {
+ throw;
+ }
+
+ continuation = () => InvokeActionResult(controllerContext, exceptionContext.Result);
+ }
+
+ return BeginInvokeAction_MakeSynchronousAsyncResult(asyncCallback, asyncState);
+ };
+
+ EndInvokeDelegate<bool> endDelegate = delegate(IAsyncResult asyncResult)
+ {
+ try
+ {
+ continuation();
+ }
+ catch (ThreadAbortException)
+ {
+ // This type of exception occurs as a result of Response.Redirect(), but we special-case so that
+ // the filters don't see this as an error.
+ throw;
+ }
+ catch (Exception ex)
+ {
+ // something blew up, so execute the exception filters
+ ExceptionContext exceptionContext = InvokeExceptionFilters(controllerContext, filterInfo.ExceptionFilters, ex);
+ if (!exceptionContext.ExceptionHandled)
+ {
+ throw;
+ }
+ InvokeActionResult(controllerContext, exceptionContext.Result);
+ }
+
+ return true;
+ };
+
+ return AsyncResultWrapper.Begin(callback, state, beginDelegate, endDelegate, _invokeActionTag);
+ }
+ else
+ {
+ // Notify the controller that no action was found.
+ return BeginInvokeAction_ActionNotFound(callback, state);
+ }
+ }
+
+ private static IAsyncResult BeginInvokeAction_ActionNotFound(AsyncCallback callback, object state)
+ {
+ BeginInvokeDelegate beginDelegate = BeginInvokeAction_MakeSynchronousAsyncResult;
+
+ EndInvokeDelegate<bool> endDelegate = delegate(IAsyncResult asyncResult)
+ {
+ return false;
+ };
+
+ return AsyncResultWrapper.Begin(callback, state, beginDelegate, endDelegate, _invokeActionTag);
+ }
+
+ private static IAsyncResult BeginInvokeAction_MakeSynchronousAsyncResult(AsyncCallback callback, object state)
+ {
+ SimpleAsyncResult asyncResult = new SimpleAsyncResult(state);
+ asyncResult.MarkCompleted(true /* completedSynchronously */, callback);
+ return asyncResult;
+ }
+
+ protected internal virtual IAsyncResult BeginInvokeActionMethod(ControllerContext controllerContext, ActionDescriptor actionDescriptor, IDictionary<string, object> parameters, AsyncCallback callback, object state)
+ {
+ AsyncActionDescriptor asyncActionDescriptor = actionDescriptor as AsyncActionDescriptor;
+ if (asyncActionDescriptor != null)
+ {
+ return BeginInvokeAsynchronousActionMethod(controllerContext, asyncActionDescriptor, parameters, callback, state);
+ }
+ else
+ {
+ return BeginInvokeSynchronousActionMethod(controllerContext, actionDescriptor, parameters, callback, state);
+ }
+ }
+
+ protected internal virtual IAsyncResult BeginInvokeActionMethodWithFilters(ControllerContext controllerContext, IList<IActionFilter> filters, ActionDescriptor actionDescriptor, IDictionary<string, object> parameters, AsyncCallback callback, object state)
+ {
+ Func<ActionExecutedContext> endContinuation = null;
+
+ BeginInvokeDelegate beginDelegate = delegate(AsyncCallback asyncCallback, object asyncState)
+ {
+ ActionExecutingContext preContext = new ActionExecutingContext(controllerContext, actionDescriptor, parameters);
+ IAsyncResult innerAsyncResult = null;
+
+ Func<Func<ActionExecutedContext>> beginContinuation = () =>
+ {
+ innerAsyncResult = BeginInvokeActionMethod(controllerContext, actionDescriptor, parameters, asyncCallback, asyncState);
+ return () =>
+ new ActionExecutedContext(controllerContext, actionDescriptor, false /* canceled */, null /* exception */)
+ {
+ Result = EndInvokeActionMethod(innerAsyncResult)
+ };
+ };
+
+ // need to reverse the filter list because the continuations are built up backward
+ Func<Func<ActionExecutedContext>> thunk = filters.Reverse().Aggregate(beginContinuation,
+ (next, filter) => () => InvokeActionMethodFilterAsynchronously(filter, preContext, next));
+ endContinuation = thunk();
+
+ if (innerAsyncResult != null)
+ {
+ // we're just waiting for the inner result to complete
+ return innerAsyncResult;
+ }
+ else
+ {
+ // something was short-circuited and the action was not called, so this was a synchronous operation
+ SimpleAsyncResult newAsyncResult = new SimpleAsyncResult(asyncState);
+ newAsyncResult.MarkCompleted(true /* completedSynchronously */, asyncCallback);
+ return newAsyncResult;
+ }
+ };
+
+ EndInvokeDelegate<ActionExecutedContext> endDelegate = delegate(IAsyncResult asyncResult)
+ {
+ return endContinuation();
+ };
+
+ return AsyncResultWrapper.Begin(callback, state, beginDelegate, endDelegate, _invokeActionMethodWithFiltersTag);
+ }
+
+ private IAsyncResult BeginInvokeAsynchronousActionMethod(ControllerContext controllerContext, AsyncActionDescriptor actionDescriptor, IDictionary<string, object> parameters, AsyncCallback callback, object state)
+ {
+ BeginInvokeDelegate beginDelegate = delegate(AsyncCallback asyncCallback, object asyncState)
+ {
+ return actionDescriptor.BeginExecute(controllerContext, parameters, asyncCallback, asyncState);
+ };
+
+ EndInvokeDelegate<ActionResult> endDelegate = delegate(IAsyncResult asyncResult)
+ {
+ object returnValue = actionDescriptor.EndExecute(asyncResult);
+ ActionResult result = CreateActionResult(controllerContext, actionDescriptor, returnValue);
+ return result;
+ };
+
+ return AsyncResultWrapper.Begin(callback, state, beginDelegate, endDelegate, _invokeActionMethodTag);
+ }
+
+ private IAsyncResult BeginInvokeSynchronousActionMethod(ControllerContext controllerContext, ActionDescriptor actionDescriptor, IDictionary<string, object> parameters, AsyncCallback callback, object state)
+ {
+ return AsyncResultWrapper.BeginSynchronous(callback, state,
+ () => InvokeSynchronousActionMethod(controllerContext, actionDescriptor, parameters),
+ _invokeActionMethodTag);
+ }
+
+ public virtual bool EndInvokeAction(IAsyncResult asyncResult)
+ {
+ return AsyncResultWrapper.End<bool>(asyncResult, _invokeActionTag);
+ }
+
+ protected internal virtual ActionResult EndInvokeActionMethod(IAsyncResult asyncResult)
+ {
+ return AsyncResultWrapper.End<ActionResult>(asyncResult, _invokeActionMethodTag);
+ }
+
+ protected internal virtual ActionExecutedContext EndInvokeActionMethodWithFilters(IAsyncResult asyncResult)
+ {
+ return AsyncResultWrapper.End<ActionExecutedContext>(asyncResult, _invokeActionMethodWithFiltersTag);
+ }
+
+ protected override ControllerDescriptor GetControllerDescriptor(ControllerContext controllerContext)
+ {
+ Type controllerType = controllerContext.Controller.GetType();
+ ControllerDescriptor controllerDescriptor = DescriptorCache.GetDescriptor(controllerType, () => new ReflectedAsyncControllerDescriptor(controllerType));
+ return controllerDescriptor;
+ }
+
+ internal static Func<ActionExecutedContext> InvokeActionMethodFilterAsynchronously(IActionFilter filter, ActionExecutingContext preContext, Func<Func<ActionExecutedContext>> nextInChain)
+ {
+ filter.OnActionExecuting(preContext);
+ if (preContext.Result != null)
+ {
+ ActionExecutedContext shortCircuitedPostContext = new ActionExecutedContext(preContext, preContext.ActionDescriptor, true /* canceled */, null /* exception */)
+ {
+ Result = preContext.Result
+ };
+ return () => shortCircuitedPostContext;
+ }
+
+ // There is a nested try / catch block here that contains much the same logic as the outer block.
+ // Since an exception can occur on either side of the asynchronous invocation, we need guards on
+ // on both sides. In the code below, the second side is represented by the nested delegate. This
+ // is really just a parallel of the synchronous ControllerActionInvoker.InvokeActionMethodFilter()
+ // method.
+
+ try
+ {
+ Func<ActionExecutedContext> continuation = nextInChain();
+
+ // add our own continuation, then return the new function
+ return () =>
+ {
+ ActionExecutedContext postContext;
+ bool wasError = true;
+
+ try
+ {
+ postContext = continuation();
+ wasError = false;
+ }
+ catch (ThreadAbortException)
+ {
+ // This type of exception occurs as a result of Response.Redirect(), but we special-case so that
+ // the filters don't see this as an error.
+ postContext = new ActionExecutedContext(preContext, preContext.ActionDescriptor, false /* canceled */, null /* exception */);
+ filter.OnActionExecuted(postContext);
+ throw;
+ }
+ catch (Exception ex)
+ {
+ postContext = new ActionExecutedContext(preContext, preContext.ActionDescriptor, false /* canceled */, ex);
+ filter.OnActionExecuted(postContext);
+ if (!postContext.ExceptionHandled)
+ {
+ throw;
+ }
+ }
+ if (!wasError)
+ {
+ filter.OnActionExecuted(postContext);
+ }
+
+ return postContext;
+ };
+ }
+ catch (ThreadAbortException)
+ {
+ // This type of exception occurs as a result of Response.Redirect(), but we special-case so that
+ // the filters don't see this as an error.
+ ActionExecutedContext postContext = new ActionExecutedContext(preContext, preContext.ActionDescriptor, false /* canceled */, null /* exception */);
+ filter.OnActionExecuted(postContext);
+ throw;
+ }
+ catch (Exception ex)
+ {
+ ActionExecutedContext postContext = new ActionExecutedContext(preContext, preContext.ActionDescriptor, false /* canceled */, ex);
+ filter.OnActionExecuted(postContext);
+ if (postContext.ExceptionHandled)
+ {
+ return () => postContext;
+ }
+ else
+ {
+ throw;
+ }
+ }
+ }
+
+ private ActionResult InvokeSynchronousActionMethod(ControllerContext controllerContext, ActionDescriptor actionDescriptor, IDictionary<string, object> parameters)
+ {
+ return InvokeActionMethod(controllerContext, actionDescriptor, parameters);
+ }
+ }
+}
diff --git a/src/System.Web.Mvc/Async/AsyncManager.cs b/src/System.Web.Mvc/Async/AsyncManager.cs
new file mode 100644
index 00000000..0e4869aa
--- /dev/null
+++ b/src/System.Web.Mvc/Async/AsyncManager.cs
@@ -0,0 +1,80 @@
+using System.Collections.Generic;
+using System.Threading;
+
+namespace System.Web.Mvc.Async
+{
+ public class AsyncManager
+ {
+ private readonly SynchronizationContext _syncContext;
+
+ /// <summary>
+ /// default timeout is 45 sec
+ /// </summary>
+ /// <remarks>
+ /// from: http://msdn.microsoft.com/en-us/library/system.web.ui.page.asynctimeout.aspx
+ /// </remarks>
+ private int _timeout = 45 * 1000;
+
+ public AsyncManager()
+ : this(null /* syncContext */)
+ {
+ }
+
+ public AsyncManager(SynchronizationContext syncContext)
+ {
+ _syncContext = syncContext ?? SynchronizationContextUtil.GetSynchronizationContext();
+
+ OutstandingOperations = new OperationCounter();
+ OutstandingOperations.Completed += delegate
+ {
+ Finish();
+ };
+
+ Parameters = new Dictionary<string, object>(StringComparer.OrdinalIgnoreCase);
+ }
+
+ public event EventHandler Finished;
+
+ public OperationCounter OutstandingOperations { get; private set; }
+
+ public IDictionary<string, object> Parameters { get; private set; }
+
+ /// <summary>
+ /// Measured in milliseconds, Timeout.Infinite means 'no timeout'
+ /// </summary>
+ public int Timeout
+ {
+ get { return _timeout; }
+ set
+ {
+ if (value < -1)
+ {
+ throw Error.AsyncCommon_InvalidTimeout("value");
+ }
+ _timeout = value;
+ }
+ }
+
+ /// <summary>
+ /// The developer may call this function to signal that all operations are complete instead of
+ /// waiting for the operation counter to reach zero.
+ /// </summary>
+ public virtual void Finish()
+ {
+ EventHandler handler = Finished;
+ if (handler != null)
+ {
+ handler(this, EventArgs.Empty);
+ }
+ }
+
+ /// <summary>
+ /// Executes a callback in the current synchronization context, which gives access to HttpContext and related items.
+ /// </summary>
+ /// <param name="action"></param>
+ public virtual void Sync(Action action)
+ {
+ _syncContext.Sync(action);
+ }
+ }
+}
diff --git a/src/System.Web.Mvc/Async/AsyncResultWrapper.cs b/src/System.Web.Mvc/Async/AsyncResultWrapper.cs
new file mode 100644
index 00000000..8cc54b7c
--- /dev/null
+++ b/src/System.Web.Mvc/Async/AsyncResultWrapper.cs
@@ -0,0 +1,303 @@
+using System.Diagnostics;
+using System.Diagnostics.CodeAnalysis;
+using System.Threading;
+
+namespace System.Web.Mvc.Async
+{
+ // This class is used for the following pattern:
+
+ // public IAsyncResult BeginInner(..., callback, state);
+ // public TInnerResult EndInner(asyncResult);
+ // public IAsyncResult BeginOuter(..., callback, state);
+ // public TOuterResult EndOuter(asyncResult);
+
+ // That is, Begin/EndOuter() wrap Begin/EndInner(), potentially with pre- and post-processing.
+
+ [DebuggerNonUserCode]
+ internal static class AsyncResultWrapper
+ {
+ // helper methods
+
+ private static Func<AsyncVoid> MakeVoidDelegate(Action action)
+ {
+ return () =>
+ {
+ action();
+ return default(AsyncVoid);
+ };
+ }
+
+ private static EndInvokeDelegate<AsyncVoid> MakeVoidDelegate(EndInvokeDelegate endDelegate)
+ {
+ return ar =>
+ {
+ endDelegate(ar);
+ return default(AsyncVoid);
+ };
+ }
+
+ // kicks off an asynchronous operation
+
+ public static IAsyncResult Begin<TResult>(AsyncCallback callback, object state, BeginInvokeDelegate beginDelegate, EndInvokeDelegate<TResult> endDelegate)
+ {
+ return Begin<TResult>(callback, state, beginDelegate, endDelegate, tag: null);
+ }
+
+ public static IAsyncResult Begin<TResult>(AsyncCallback callback, object state, BeginInvokeDelegate beginDelegate, EndInvokeDelegate<TResult> endDelegate, object tag)
+ {
+ return Begin<TResult>(callback, state, beginDelegate, endDelegate, tag, Timeout.Infinite);
+ }
+
+ public static IAsyncResult Begin<TResult>(AsyncCallback callback, object state, BeginInvokeDelegate beginDelegate, EndInvokeDelegate<TResult> endDelegate, object tag, int timeout)
+ {
+ WrappedAsyncResult<TResult> asyncResult = new WrappedAsyncResult<TResult>(beginDelegate, endDelegate, tag);
+ asyncResult.Begin(callback, state, timeout);
+ return asyncResult;
+ }
+
+ public static IAsyncResult Begin(AsyncCallback callback, object state, BeginInvokeDelegate beginDelegate, EndInvokeDelegate endDelegate)
+ {
+ return Begin(callback, state, beginDelegate, endDelegate, tag: null);
+ }
+
+ public static IAsyncResult Begin(AsyncCallback callback, object state, BeginInvokeDelegate beginDelegate, EndInvokeDelegate endDelegate, object tag)
+ {
+ return Begin(callback, state, beginDelegate, endDelegate, tag, Timeout.Infinite);
+ }
+
+ public static IAsyncResult Begin(AsyncCallback callback, object state, BeginInvokeDelegate beginDelegate, EndInvokeDelegate endDelegate, object tag, int timeout)
+ {
+ return Begin<AsyncVoid>(callback, state, beginDelegate, MakeVoidDelegate(endDelegate), tag, timeout);
+ }
+
+ // wraps a synchronous operation in an asynchronous wrapper, but still completes synchronously
+
+ public static IAsyncResult BeginSynchronous<TResult>(AsyncCallback callback, object state, Func<TResult> func)
+ {
+ return BeginSynchronous<TResult>(callback, state, func, tag: null);
+ }
+
+ public static IAsyncResult BeginSynchronous<TResult>(AsyncCallback callback, object state, Func<TResult> func, object tag)
+ {
+ // Begin() doesn't perform any work on its own and returns immediately.
+ BeginInvokeDelegate beginDelegate = (asyncCallback, asyncState) =>
+ {
+ SimpleAsyncResult innerAsyncResult = new SimpleAsyncResult(asyncState);
+ innerAsyncResult.MarkCompleted(completedSynchronously: true, callback: asyncCallback);
+ return innerAsyncResult;
+ };
+
+ // The End() method blocks.
+ EndInvokeDelegate<TResult> endDelegate = _ =>
+ {
+ return func();
+ };
+
+ WrappedAsyncResult<TResult> asyncResult = new WrappedAsyncResult<TResult>(beginDelegate, endDelegate, tag);
+ asyncResult.Begin(callback, state, Timeout.Infinite);
+ return asyncResult;
+ }
+
+ public static IAsyncResult BeginSynchronous(AsyncCallback callback, object state, Action action)
+ {
+ return BeginSynchronous(callback, state, action, tag: null);
+ }
+
+ public static IAsyncResult BeginSynchronous(AsyncCallback callback, object state, Action action, object tag)
+ {
+ return BeginSynchronous<AsyncVoid>(callback, state, MakeVoidDelegate(action), tag);
+ }
+
+ // completes an asynchronous operation
+
+ public static TResult End<TResult>(IAsyncResult asyncResult)
+ {
+ return End<TResult>(asyncResult, tag: null);
+ }
+
+ public static TResult End<TResult>(IAsyncResult asyncResult, object tag)
+ {
+ return WrappedAsyncResult<TResult>.Cast(asyncResult, tag).End();
+ }
+
+ public static void End(IAsyncResult asyncResult)
+ {
+ End(asyncResult, tag: null);
+ }
+
+ public static void End(IAsyncResult asyncResult, object tag)
+ {
+ End<AsyncVoid>(asyncResult, tag);
+ }
+
+ [DebuggerNonUserCode]
+ [SuppressMessage("Microsoft.Design", "CA1001:TypesThatOwnDisposableFieldsShouldBeDisposable", Justification = "The Timer will be disposed of either when it fires or when the operation completes successfully.")]
+ private sealed class WrappedAsyncResult<TResult> : IAsyncResult
+ {
+ private const int AsyncStateNone = 0;
+ private const int AsyncStateBeginUnwound = 1;
+ private const int AsyncStateCallbackFired = 2;
+
+ private int _asyncState;
+ private readonly BeginInvokeDelegate _beginDelegate;
+ private readonly object _beginDelegateLockObj = new object();
+ private readonly EndInvokeDelegate<TResult> _endDelegate;
+ private readonly SingleEntryGate _endExecutedGate = new SingleEntryGate(); // prevent End() from being called twice
+ private readonly SingleEntryGate _handleCallbackGate = new SingleEntryGate(); // prevent callback from being handled multiple times
+ private readonly object _tag; // prevent an instance of this type from being passed to the wrong End() method
+ private IAsyncResult _innerAsyncResult;
+ private AsyncCallback _originalCallback;
+ private volatile bool _timedOut;
+ private Timer _timer;
+
+ public WrappedAsyncResult(BeginInvokeDelegate beginDelegate, EndInvokeDelegate<TResult> endDelegate, object tag)
+ {
+ _beginDelegate = beginDelegate;
+ _endDelegate = endDelegate;
+ _tag = tag;
+ }
+
+ public object AsyncState
+ {
+ get { return _innerAsyncResult.AsyncState; }
+ }
+
+ public WaitHandle AsyncWaitHandle
+ {
+ get { return _innerAsyncResult.AsyncWaitHandle; }
+ }
+
+ public bool CompletedSynchronously { get; private set; }
+
+ public bool IsCompleted
+ {
+ get { return _innerAsyncResult.IsCompleted; }
+ }
+
+ // kicks off the process, instantiates a timer if requested
+ public void Begin(AsyncCallback callback, object state, int timeout)
+ {
+ _originalCallback = callback;
+
+ // Force the target Begin() operation to complete before the callback can continue,
+ // since the target operation might perform post-processing of the data.
+ lock (_beginDelegateLockObj)
+ {
+ _innerAsyncResult = _beginDelegate(HandleAsynchronousCompletion, state);
+
+ // If the callback has already fired, then the completion routine has no-oped and we
+ // can just treat this as if it were a normal synchronous completion.
+ int originalState = Interlocked.Exchange(ref _asyncState, AsyncStateBeginUnwound);
+ bool callbackAlreadyFired = (originalState == AsyncStateCallbackFired);
+
+ CompletedSynchronously = callbackAlreadyFired || _innerAsyncResult.CompletedSynchronously;
+
+ if (!CompletedSynchronously)
+ {
+ if (timeout > Timeout.Infinite)
+ {
+ CreateTimer(timeout);
+ }
+ }
+ }
+
+ if (CompletedSynchronously)
+ {
+ if (callback != null)
+ {
+ callback(this);
+ }
+ }
+ }
+
+ public static WrappedAsyncResult<TResult> Cast(IAsyncResult asyncResult, object tag)
+ {
+ if (asyncResult == null)
+ {
+ throw new ArgumentNullException("asyncResult");
+ }
+
+ WrappedAsyncResult<TResult> castResult = asyncResult as WrappedAsyncResult<TResult>;
+ if (castResult != null && Equals(castResult._tag, tag))
+ {
+ return castResult;
+ }
+ else
+ {
+ throw Error.AsyncCommon_InvalidAsyncResult("asyncResult");
+ }
+ }
+
+ private void CreateTimer(int timeout)
+ {
+ // this method should be called within a lock(_beginDelegateLockObj)
+ _timer = new Timer(HandleTimeout, null, timeout, Timeout.Infinite /* disable periodic signaling */);
+ }
+
+ public TResult End()
+ {
+ if (!_endExecutedGate.TryEnter())
+ {
+ throw Error.AsyncCommon_AsyncResultAlreadyConsumed();
+ }
+
+ if (_timedOut)
+ {
+ throw new TimeoutException();
+ }
+ WaitForBeginToCompleteAndDestroyTimer();
+
+ return _endDelegate(_innerAsyncResult);
+ }
+
+ private void ExecuteAsynchronousCallback(bool timedOut)
+ {
+ WaitForBeginToCompleteAndDestroyTimer();
+
+ if (_handleCallbackGate.TryEnter())
+ {
+ _timedOut = timedOut;
+ if (_originalCallback != null)
+ {
+ _originalCallback(this);
+ }
+ }
+ }
+
+ private void HandleAsynchronousCompletion(IAsyncResult asyncResult)
+ {
+ // Transition the async state to CALLBACK_FIRED. If the Begin* method hasn't yet unwound,
+ // then we can no-op here since the Begin method will query the _asyncState field and
+ // treat this as a regular synchronous completion.
+ int originalState = Interlocked.Exchange(ref _asyncState, AsyncStateCallbackFired);
+ if (originalState != AsyncStateBeginUnwound)
+ {
+ return;
+ }
+
+ ExecuteAsynchronousCallback(timedOut: false);
+ }
+
+ private void HandleTimeout(object state)
+ {
+ ExecuteAsynchronousCallback(timedOut: true);
+ }
+
+ private void WaitForBeginToCompleteAndDestroyTimer()
+ {
+ lock (_beginDelegateLockObj)
+ {
+ // Wait for the target Begin() method to complete, as it might be performing
+ // post-processing. This also forces a memory barrier, so _innerAsyncResult
+ // is guaranteed to be non-null at this point.
+
+ if (_timer != null)
+ {
+ _timer.Dispose();
+ }
+ _timer = null;
+ }
+ }
+ }
+ }
+}
diff --git a/src/System.Web.Mvc/Async/AsyncUtil.cs b/src/System.Web.Mvc/Async/AsyncUtil.cs
new file mode 100644
index 00000000..4c75be0b
--- /dev/null
+++ b/src/System.Web.Mvc/Async/AsyncUtil.cs
@@ -0,0 +1,31 @@
+using System.Threading;
+
+namespace System.Web.Mvc.Async
+{
+ internal static class AsyncUtil
+ {
+ public static AsyncCallback WrapCallbackForSynchronizedExecution(AsyncCallback callback, SynchronizationContext syncContext)
+ {
+ if (callback == null || syncContext == null)
+ {
+ return callback;
+ }
+
+ AsyncCallback newCallback = delegate(IAsyncResult asyncResult)
+ {
+ if (asyncResult.CompletedSynchronously)
+ {
+ callback(asyncResult);
+ }
+ else
+ {
+ // Only take the application lock if this request completed asynchronously,
+ // else we might end up in a deadlock situation.
+ syncContext.Sync(() => callback(asyncResult));
+ }
+ };
+
+ return newCallback;
+ }
+ }
+}
diff --git a/src/System.Web.Mvc/Async/AsyncVoid.cs b/src/System.Web.Mvc/Async/AsyncVoid.cs
new file mode 100644
index 00000000..697438cc
--- /dev/null
+++ b/src/System.Web.Mvc/Async/AsyncVoid.cs
@@ -0,0 +1,7 @@
+namespace System.Web.Mvc.Async
+{
+ // Dummy type used for passing something resembling 'void' to the async delegate functions
+ internal struct AsyncVoid
+ {
+ }
+}
diff --git a/src/System.Web.Mvc/Async/BeginInvokeDelegate.cs b/src/System.Web.Mvc/Async/BeginInvokeDelegate.cs
new file mode 100644
index 00000000..4f119f4a
--- /dev/null
+++ b/src/System.Web.Mvc/Async/BeginInvokeDelegate.cs
@@ -0,0 +1,4 @@
+namespace System.Web.Mvc.Async
+{
+ internal delegate IAsyncResult BeginInvokeDelegate(AsyncCallback callback, object state);
+}
diff --git a/src/System.Web.Mvc/Async/EndInvokeDelegate.cs b/src/System.Web.Mvc/Async/EndInvokeDelegate.cs
new file mode 100644
index 00000000..fd037258
--- /dev/null
+++ b/src/System.Web.Mvc/Async/EndInvokeDelegate.cs
@@ -0,0 +1,4 @@
+namespace System.Web.Mvc.Async
+{
+ internal delegate void EndInvokeDelegate(IAsyncResult asyncResult);
+}
diff --git a/src/System.Web.Mvc/Async/EndInvokeDelegate`1.cs b/src/System.Web.Mvc/Async/EndInvokeDelegate`1.cs
new file mode 100644
index 00000000..e2650680
--- /dev/null
+++ b/src/System.Web.Mvc/Async/EndInvokeDelegate`1.cs
@@ -0,0 +1,4 @@
+namespace System.Web.Mvc.Async
+{
+ internal delegate TResult EndInvokeDelegate<TResult>(IAsyncResult asyncResult);
+}
diff --git a/src/System.Web.Mvc/Async/IAsyncActionInvoker.cs b/src/System.Web.Mvc/Async/IAsyncActionInvoker.cs
new file mode 100644
index 00000000..c9ee72c3
--- /dev/null
+++ b/src/System.Web.Mvc/Async/IAsyncActionInvoker.cs
@@ -0,0 +1,8 @@
+namespace System.Web.Mvc.Async
+{
+ public interface IAsyncActionInvoker : IActionInvoker
+ {
+ IAsyncResult BeginInvokeAction(ControllerContext controllerContext, string actionName, AsyncCallback callback, object state);
+ bool EndInvokeAction(IAsyncResult asyncResult);
+ }
+}
diff --git a/src/System.Web.Mvc/Async/IAsyncController.cs b/src/System.Web.Mvc/Async/IAsyncController.cs
new file mode 100644
index 00000000..ce46fce4
--- /dev/null
+++ b/src/System.Web.Mvc/Async/IAsyncController.cs
@@ -0,0 +1,10 @@
+using System.Web.Routing;
+
+namespace System.Web.Mvc.Async
+{
+ public interface IAsyncController : IController
+ {
+ IAsyncResult BeginExecute(RequestContext requestContext, AsyncCallback callback, object state);
+ void EndExecute(IAsyncResult asyncResult);
+ }
+}
diff --git a/src/System.Web.Mvc/Async/IAsyncManagerContainer.cs b/src/System.Web.Mvc/Async/IAsyncManagerContainer.cs
new file mode 100644
index 00000000..9e53fd12
--- /dev/null
+++ b/src/System.Web.Mvc/Async/IAsyncManagerContainer.cs
@@ -0,0 +1,7 @@
+namespace System.Web.Mvc.Async
+{
+ public interface IAsyncManagerContainer
+ {
+ AsyncManager AsyncManager { get; }
+ }
+}
diff --git a/src/System.Web.Mvc/Async/OperationCounter.cs b/src/System.Web.Mvc/Async/OperationCounter.cs
new file mode 100644
index 00000000..9dbb63b5
--- /dev/null
+++ b/src/System.Web.Mvc/Async/OperationCounter.cs
@@ -0,0 +1,56 @@
+using System.Threading;
+
+namespace System.Web.Mvc.Async
+{
+ public sealed class OperationCounter
+ {
+ private int _count;
+
+ public event EventHandler Completed;
+
+ public int Count
+ {
+ get { return Thread.VolatileRead(ref _count); }
+ }
+
+ private int AddAndExecuteCallbackIfCompleted(int value)
+ {
+ int newCount = Interlocked.Add(ref _count, value);
+ if (newCount == 0)
+ {
+ OnCompleted();
+ }
+
+ return newCount;
+ }
+
+ public int Decrement()
+ {
+ return AddAndExecuteCallbackIfCompleted(-1);
+ }
+
+ public int Decrement(int value)
+ {
+ return AddAndExecuteCallbackIfCompleted(-value);
+ }
+
+ public int Increment()
+ {
+ return AddAndExecuteCallbackIfCompleted(1);
+ }
+
+ public int Increment(int value)
+ {
+ return AddAndExecuteCallbackIfCompleted(value);
+ }
+
+ private void OnCompleted()
+ {
+ EventHandler handler = Completed;
+ if (handler != null)
+ {
+ handler(this, EventArgs.Empty);
+ }
+ }
+ }
+}
diff --git a/src/System.Web.Mvc/Async/ReflectedAsyncActionDescriptor.cs b/src/System.Web.Mvc/Async/ReflectedAsyncActionDescriptor.cs
new file mode 100644
index 00000000..b1380e84
--- /dev/null
+++ b/src/System.Web.Mvc/Async/ReflectedAsyncActionDescriptor.cs
@@ -0,0 +1,188 @@
+using System.Collections.Generic;
+using System.Linq;
+using System.Reflection;
+using System.Threading;
+
+namespace System.Web.Mvc.Async
+{
+ public class ReflectedAsyncActionDescriptor : AsyncActionDescriptor
+ {
+ private readonly object _executeTag = new object();
+
+ private readonly string _actionName;
+ private readonly ControllerDescriptor _controllerDescriptor;
+ private readonly Lazy<string> _uniqueId;
+ private ParameterDescriptor[] _parametersCache;
+
+ public ReflectedAsyncActionDescriptor(MethodInfo asyncMethodInfo, MethodInfo completedMethodInfo, string actionName, ControllerDescriptor controllerDescriptor)
+ : this(asyncMethodInfo, completedMethodInfo, actionName, controllerDescriptor, true /* validateMethods */)
+ {
+ }
+
+ internal ReflectedAsyncActionDescriptor(MethodInfo asyncMethodInfo, MethodInfo completedMethodInfo, string actionName, ControllerDescriptor controllerDescriptor, bool validateMethods)
+ {
+ if (asyncMethodInfo == null)
+ {
+ throw new ArgumentNullException("asyncMethodInfo");
+ }
+ if (completedMethodInfo == null)
+ {
+ throw new ArgumentNullException("completedMethodInfo");
+ }
+ if (String.IsNullOrEmpty(actionName))
+ {
+ throw Error.ParameterCannotBeNullOrEmpty("actionName");
+ }
+ if (controllerDescriptor == null)
+ {
+ throw new ArgumentNullException("controllerDescriptor");
+ }
+
+ if (validateMethods)
+ {
+ string asyncFailedMessage = VerifyActionMethodIsCallable(asyncMethodInfo);
+ if (asyncFailedMessage != null)
+ {
+ throw new ArgumentException(asyncFailedMessage, "asyncMethodInfo");
+ }
+
+ string completedFailedMessage = VerifyActionMethodIsCallable(completedMethodInfo);
+ if (completedFailedMessage != null)
+ {
+ throw new ArgumentException(completedFailedMessage, "completedMethodInfo");
+ }
+ }
+
+ AsyncMethodInfo = asyncMethodInfo;
+ CompletedMethodInfo = completedMethodInfo;
+ _actionName = actionName;
+ _controllerDescriptor = controllerDescriptor;
+ _uniqueId = new Lazy<string>(CreateUniqueId);
+ }
+
+ public override string ActionName
+ {
+ get { return _actionName; }
+ }
+
+ public MethodInfo AsyncMethodInfo { get; private set; }
+
+ public MethodInfo CompletedMethodInfo { get; private set; }
+
+ public override ControllerDescriptor ControllerDescriptor
+ {
+ get { return _controllerDescriptor; }
+ }
+
+ public override string UniqueId
+ {
+ get { return _uniqueId.Value; }
+ }
+
+ public override IAsyncResult BeginExecute(ControllerContext controllerContext, IDictionary<string, object> parameters, AsyncCallback callback, object state)
+ {
+ if (controllerContext == null)
+ {
+ throw new ArgumentNullException("controllerContext");
+ }
+ if (parameters == null)
+ {
+ throw new ArgumentNullException("parameters");
+ }
+
+ AsyncManager asyncManager = GetAsyncManager(controllerContext.Controller);
+
+ BeginInvokeDelegate beginDelegate = delegate(AsyncCallback asyncCallback, object asyncState)
+ {
+ // call the XxxAsync() method
+ ParameterInfo[] parameterInfos = AsyncMethodInfo.GetParameters();
+ var rawParameterValues = from parameterInfo in parameterInfos
+ select ExtractParameterFromDictionary(parameterInfo, parameters, AsyncMethodInfo);
+ object[] parametersArray = rawParameterValues.ToArray();
+
+ TriggerListener listener = new TriggerListener();
+ SimpleAsyncResult asyncResult = new SimpleAsyncResult(asyncState);
+
+ // hook the Finished event to notify us upon completion
+ Trigger finishTrigger = listener.CreateTrigger();
+ asyncManager.Finished += delegate
+ {
+ finishTrigger.Fire();
+ };
+ asyncManager.OutstandingOperations.Increment();
+
+ // to simplify the logic, force the rest of the pipeline to execute in an asynchronous callback
+ listener.SetContinuation(() => ThreadPool.QueueUserWorkItem(_ => asyncResult.MarkCompleted(false /* completedSynchronously */, asyncCallback)));
+
+ // the inner operation might complete synchronously, so all setup work has to be done before this point
+ ActionMethodDispatcher dispatcher = DispatcherCache.GetDispatcher(AsyncMethodInfo);
+ dispatcher.Execute(controllerContext.Controller, parametersArray); // ignore return value from this method
+
+ // now that the XxxAsync() method has completed, kick off any pending operations
+ asyncManager.OutstandingOperations.Decrement();
+ listener.Activate();
+ return asyncResult;
+ };
+
+ EndInvokeDelegate<object> endDelegate = delegate(IAsyncResult asyncResult)
+ {
+ // call the XxxCompleted() method
+ ParameterInfo[] completionParametersInfos = CompletedMethodInfo.GetParameters();
+ var rawCompletionParameterValues = from parameterInfo in completionParametersInfos
+ select ExtractParameterOrDefaultFromDictionary(parameterInfo, asyncManager.Parameters);
+ object[] completionParametersArray = rawCompletionParameterValues.ToArray();
+
+ ActionMethodDispatcher dispatcher = DispatcherCache.GetDispatcher(CompletedMethodInfo);
+ object actionReturnValue = dispatcher.Execute(controllerContext.Controller, completionParametersArray);
+ return actionReturnValue;
+ };
+
+ return AsyncResultWrapper.Begin(callback, state, beginDelegate, endDelegate, _executeTag, asyncManager.Timeout);
+ }
+
+ private string CreateUniqueId()
+ {
+ return base.UniqueId + DescriptorUtil.CreateUniqueId(AsyncMethodInfo, CompletedMethodInfo);
+ }
+
+ public override object EndExecute(IAsyncResult asyncResult)
+ {
+ return AsyncResultWrapper.End<object>(asyncResult, _executeTag);
+ }
+
+ public override object[] GetCustomAttributes(bool inherit)
+ {
+ return ActionDescriptorHelper.GetCustomAttributes(AsyncMethodInfo, inherit);
+ }
+
+ public override object[] GetCustomAttributes(Type attributeType, bool inherit)
+ {
+ return ActionDescriptorHelper.GetCustomAttributes(AsyncMethodInfo, attributeType, inherit);
+ }
+
+ public override IEnumerable<FilterAttribute> GetFilterAttributes(bool useCache)
+ {
+ if (useCache && GetType() == typeof(ReflectedAsyncActionDescriptor))
+ {
+ // Do not look at cache in types derived from this type because they might incorrectly implement GetCustomAttributes
+ return ReflectedAttributeCache.GetMethodFilterAttributes(AsyncMethodInfo);
+ }
+ return base.GetFilterAttributes(useCache);
+ }
+
+ public override ParameterDescriptor[] GetParameters()
+ {
+ return ActionDescriptorHelper.GetParameters(this, AsyncMethodInfo, ref _parametersCache);
+ }
+
+ public override ICollection<ActionSelector> GetSelectors()
+ {
+ return ActionDescriptorHelper.GetSelectors(AsyncMethodInfo);
+ }
+
+ public override bool IsDefined(Type attributeType, bool inherit)
+ {
+ return ActionDescriptorHelper.IsDefined(AsyncMethodInfo, attributeType, inherit);
+ }
+ }
+}
diff --git a/src/System.Web.Mvc/Async/ReflectedAsyncControllerDescriptor.cs b/src/System.Web.Mvc/Async/ReflectedAsyncControllerDescriptor.cs
new file mode 100644
index 00000000..de60cdbf
--- /dev/null
+++ b/src/System.Web.Mvc/Async/ReflectedAsyncControllerDescriptor.cs
@@ -0,0 +1,104 @@
+using System.Collections.Generic;
+
+namespace System.Web.Mvc.Async
+{
+ public class ReflectedAsyncControllerDescriptor : ControllerDescriptor
+ {
+ private static readonly ActionDescriptor[] _emptyCanonicalActions = new ActionDescriptor[0];
+
+ private readonly Type _controllerType;
+ private readonly AsyncActionMethodSelector _selector;
+
+ public ReflectedAsyncControllerDescriptor(Type controllerType)
+ {
+ if (controllerType == null)
+ {
+ throw new ArgumentNullException("controllerType");
+ }
+
+ _controllerType = controllerType;
+ bool allowLegacyAsyncActions = AllowLegacyAsyncActions(_controllerType);
+ _selector = new AsyncActionMethodSelector(_controllerType, allowLegacyAsyncActions);
+ }
+
+ public sealed override Type ControllerType
+ {
+ get { return _controllerType; }
+ }
+
+ /// <summary>
+ /// Determines if we should bind "Foo" to FooAsync/FooCompleted pattern.
+ /// </summary>
+ /// <param name="controllerType"></param>
+ /// <returns></returns>
+ private static bool AllowLegacyAsyncActions(Type controllerType)
+ {
+ if (typeof(AsyncController).IsAssignableFrom(controllerType))
+ {
+ return true;
+ }
+ if (typeof(Controller).IsAssignableFrom(controllerType))
+ {
+ // for backwards compat. Controller now supports IAsyncController,
+ // but still use synchronous bindings patterns.
+ return false;
+ }
+ if (!typeof(IAsyncController).IsAssignableFrom(controllerType))
+ {
+ return false;
+ }
+ return true;
+ }
+
+ public override ActionDescriptor FindAction(ControllerContext controllerContext, string actionName)
+ {
+ if (controllerContext == null)
+ {
+ throw new ArgumentNullException("controllerContext");
+ }
+ if (String.IsNullOrEmpty(actionName))
+ {
+ throw Error.ParameterCannotBeNullOrEmpty("actionName");
+ }
+
+ ActionDescriptorCreator creator = _selector.FindAction(controllerContext, actionName);
+ if (creator == null)
+ {
+ return null;
+ }
+
+ return creator(actionName, this);
+ }
+
+ public override ActionDescriptor[] GetCanonicalActions()
+ {
+ // everything is looked up dymanically, so there are no 'canonical' actions
+ return _emptyCanonicalActions;
+ }
+
+ public override object[] GetCustomAttributes(bool inherit)
+ {
+ return ControllerType.GetCustomAttributes(inherit);
+ }
+
+ public override object[] GetCustomAttributes(Type attributeType, bool inherit)
+ {
+ return ControllerType.GetCustomAttributes(attributeType, inherit);
+ }
+
+ public override IEnumerable<FilterAttribute> GetFilterAttributes(bool useCache)
+ {
+ if (useCache && GetType() == typeof(ReflectedAsyncControllerDescriptor))
+ {
+ // Do not look at cache in types derived from this type because they might incorrectly implement GetCustomAttributes
+ return ReflectedAttributeCache.GetTypeFilterAttributes(ControllerType);
+ }
+ return base.GetFilterAttributes(useCache);
+ }
+
+ public override bool IsDefined(Type attributeType, bool inherit)
+ {
+ return ControllerType.IsDefined(attributeType, inherit);
+ }
+ }
+}
diff --git a/src/System.Web.Mvc/Async/SimpleAsyncResult.cs b/src/System.Web.Mvc/Async/SimpleAsyncResult.cs
new file mode 100644
index 00000000..3e4df09a
--- /dev/null
+++ b/src/System.Web.Mvc/Async/SimpleAsyncResult.cs
@@ -0,0 +1,53 @@
+using System.Threading;
+
+namespace System.Web.Mvc.Async
+{
+ internal sealed class SimpleAsyncResult : IAsyncResult
+ {
+ private readonly object _asyncState;
+ private bool _completedSynchronously;
+ private volatile bool _isCompleted;
+
+ public SimpleAsyncResult(object asyncState)
+ {
+ _asyncState = asyncState;
+ }
+
+ public object AsyncState
+ {
+ get { return _asyncState; }
+ }
+
+ // ASP.NET IAsyncResult objects should never expose a WaitHandle due to potential deadlocking
+ public WaitHandle AsyncWaitHandle
+ {
+ get { return null; }
+ }
+
+ public bool CompletedSynchronously
+ {
+ get { return _completedSynchronously; }
+ }
+
+ public bool IsCompleted
+ {
+ get { return _isCompleted; }
+ }
+
+ // Proper order of execution:
+ // 1. Set the CompletedSynchronously property to the correct value
+ // 2. Set the IsCompleted flag
+ // 3. Execute the callback
+ // 4. Signal the WaitHandle (which we don't have)
+ public void MarkCompleted(bool completedSynchronously, AsyncCallback callback)
+ {
+ _completedSynchronously = completedSynchronously;
+ _isCompleted = true;
+
+ if (callback != null)
+ {
+ callback(this);
+ }
+ }
+ }
+}
diff --git a/src/System.Web.Mvc/Async/SingleEntryGate.cs b/src/System.Web.Mvc/Async/SingleEntryGate.cs
new file mode 100644
index 00000000..746593b4
--- /dev/null
+++ b/src/System.Web.Mvc/Async/SingleEntryGate.cs
@@ -0,0 +1,20 @@
+using System.Threading;
+
+namespace System.Web.Mvc.Async
+{
+ // used to synchronize access to a single-use consumable resource
+ internal sealed class SingleEntryGate
+ {
+ private const int NotEntered = 0;
+ private const int Entered = 1;
+
+ private int _status;
+
+ // returns true if this is the first call to TryEnter(), false otherwise
+ public bool TryEnter()
+ {
+ int oldStatus = Interlocked.Exchange(ref _status, Entered);
+ return (oldStatus == NotEntered);
+ }
+ }
+}
diff --git a/src/System.Web.Mvc/Async/SynchronizationContextUtil.cs b/src/System.Web.Mvc/Async/SynchronizationContextUtil.cs
new file mode 100644
index 00000000..f64a9f6b
--- /dev/null
+++ b/src/System.Web.Mvc/Async/SynchronizationContextUtil.cs
@@ -0,0 +1,52 @@
+using System.Diagnostics.CodeAnalysis;
+using System.Threading;
+
+namespace System.Web.Mvc.Async
+{
+ internal static class SynchronizationContextUtil
+ {
+ public static SynchronizationContext GetSynchronizationContext()
+ {
+ // In a runtime environment, SynchronizationContext.Current will be set to an instance
+ // of AspNetSynchronizationContext. In a unit test environment, the Current property
+ // won't be set and we have to create one on the fly.
+ return SynchronizationContext.Current ?? new SynchronizationContext();
+ }
+
+ [SuppressMessage("Microsoft.Design", "CA1031:DoNotCatchGeneralExceptionTypes", Justification = "The exception is swallowed and immediately re-thrown")]
+ public static T Sync<T>(this SynchronizationContext syncContext, Func<T> func)
+ {
+ T theValue = default(T);
+ Exception thrownException = null;
+
+ syncContext.Send(o =>
+ {
+ try
+ {
+ theValue = func();
+ }
+ catch (Exception ex)
+ {
+ // by default, the AspNetSynchronizationContext type will swallow thrown exceptions,
+ // so we need to save and propagate them
+ thrownException = ex;
+ }
+ }, null);
+
+ if (thrownException != null)
+ {
+ throw Error.SynchronizationContextUtil_ExceptionThrown(thrownException);
+ }
+ return theValue;
+ }
+
+ public static void Sync(this SynchronizationContext syncContext, Action action)
+ {
+ Sync<AsyncVoid>(syncContext, () =>
+ {
+ action();
+ return default(AsyncVoid);
+ });
+ }
+ }
+}
diff --git a/src/System.Web.Mvc/Async/SynchronousOperationException.cs b/src/System.Web.Mvc/Async/SynchronousOperationException.cs
new file mode 100644
index 00000000..b7ac88e1
--- /dev/null
+++ b/src/System.Web.Mvc/Async/SynchronousOperationException.cs
@@ -0,0 +1,30 @@
+using System.Runtime.Serialization;
+
+namespace System.Web.Mvc.Async
+{
+ // This exception type is thrown by the SynchronizationContextUtil helper class since the AspNetSynchronizationContext
+ // type swallows exceptions. The inner exception contains the data the user cares about.
+
+ [Serializable]
+ public sealed class SynchronousOperationException : HttpException
+ {
+ public SynchronousOperationException()
+ {
+ }
+
+ private SynchronousOperationException(SerializationInfo info, StreamingContext context)
+ : base(info, context)
+ {
+ }
+
+ public SynchronousOperationException(string message)
+ : base(message)
+ {
+ }
+
+ public SynchronousOperationException(string message, Exception innerException)
+ : base(message, innerException)
+ {
+ }
+ }
+}
diff --git a/src/System.Web.Mvc/Async/TaskAsyncActionDescriptor.cs b/src/System.Web.Mvc/Async/TaskAsyncActionDescriptor.cs
new file mode 100644
index 00000000..22e8e8b1
--- /dev/null
+++ b/src/System.Web.Mvc/Async/TaskAsyncActionDescriptor.cs
@@ -0,0 +1,264 @@
+using System.Collections.Concurrent;
+using System.Collections.Generic;
+using System.Globalization;
+using System.Linq;
+using System.Linq.Expressions;
+using System.Reflection;
+using System.Threading;
+using System.Threading.Tasks;
+using System.Web.Mvc.Properties;
+
+namespace System.Web.Mvc.Async
+{
+ /// <summary>
+ /// When an action method returns either Task or Task{T} the TaskAsyncActionDescriptor provides information about the action.
+ /// </summary>
+ public class TaskAsyncActionDescriptor : AsyncActionDescriptor
+ {
+ /// <summary>
+ /// dictionary to hold methods that can read Task{T}.Result
+ /// </summary>
+ private static readonly ConcurrentDictionary<Type, Func<object, object>> _taskValueExtractors = new ConcurrentDictionary<Type, Func<object, object>>();
+ private readonly string _actionName;
+ private readonly ControllerDescriptor _controllerDescriptor;
+ private readonly Lazy<string> _uniqueId;
+ private ParameterDescriptor[] _parametersCache;
+
+ public TaskAsyncActionDescriptor(MethodInfo taskMethodInfo, string actionName, ControllerDescriptor controllerDescriptor)
+ : this(taskMethodInfo, actionName, controllerDescriptor, validateMethod: true)
+ {
+ }
+
+ internal TaskAsyncActionDescriptor(MethodInfo taskMethodInfo, string actionName, ControllerDescriptor controllerDescriptor, bool validateMethod)
+ {
+ if (taskMethodInfo == null)
+ {
+ throw new ArgumentNullException("taskMethodInfo");
+ }
+ if (String.IsNullOrEmpty(actionName))
+ {
+ throw Error.ParameterCannotBeNullOrEmpty("actionName");
+ }
+ if (controllerDescriptor == null)
+ {
+ throw new ArgumentNullException("controllerDescriptor");
+ }
+
+ if (validateMethod)
+ {
+ string taskFailedMessage = VerifyActionMethodIsCallable(taskMethodInfo);
+ if (taskFailedMessage != null)
+ {
+ throw new ArgumentException(taskFailedMessage, "taskMethodInfo");
+ }
+ }
+
+ TaskMethodInfo = taskMethodInfo;
+ _actionName = actionName;
+ _controllerDescriptor = controllerDescriptor;
+ _uniqueId = new Lazy<string>(CreateUniqueId);
+ }
+
+ public override string ActionName
+ {
+ get { return _actionName; }
+ }
+
+ public MethodInfo TaskMethodInfo { get; private set; }
+
+ public override ControllerDescriptor ControllerDescriptor
+ {
+ get { return _controllerDescriptor; }
+ }
+
+ public override string UniqueId
+ {
+ get { return _uniqueId.Value; }
+ }
+
+ private string CreateUniqueId()
+ {
+ return base.UniqueId + DescriptorUtil.CreateUniqueId(TaskMethodInfo);
+ }
+
+ public override IAsyncResult BeginExecute(ControllerContext controllerContext, IDictionary<string, object> parameters, AsyncCallback callback, object state)
+ {
+ if (controllerContext == null)
+ {
+ throw new ArgumentNullException("controllerContext");
+ }
+ if (parameters == null)
+ {
+ throw new ArgumentNullException("parameters");
+ }
+
+ ParameterInfo[] parameterInfos = TaskMethodInfo.GetParameters();
+ var rawParameterValues = from parameterInfo in parameterInfos
+ select ExtractParameterFromDictionary(parameterInfo, parameters, TaskMethodInfo);
+ object[] parametersArray = rawParameterValues.ToArray();
+
+ CancellationTokenSource tokenSource = null;
+ bool disposedTimer = false;
+ Timer taskCancelledTimer = null;
+ bool taskCancelledTimerRequired = false;
+
+ int timeout = GetAsyncManager(controllerContext.Controller).Timeout;
+
+ for (int i = 0; i < parametersArray.Length; i++)
+ {
+ if (default(CancellationToken).Equals(parametersArray[i]))
+ {
+ tokenSource = new CancellationTokenSource();
+ parametersArray[i] = tokenSource.Token;
+
+ // If there is a timeout we will create a timer to cancel the task when the
+ // timeout expires.
+ taskCancelledTimerRequired = timeout > Timeout.Infinite;
+ break;
+ }
+ }
+
+ ActionMethodDispatcher dispatcher = DispatcherCache.GetDispatcher(TaskMethodInfo);
+
+ if (taskCancelledTimerRequired)
+ {
+ taskCancelledTimer = new Timer(_ =>
+ {
+ lock (tokenSource)
+ {
+ if (!disposedTimer)
+ {
+ tokenSource.Cancel();
+ }
+ }
+ },
+ state: null, dueTime: timeout, period: Timeout.Infinite);
+ }
+
+ Task taskUser = dispatcher.Execute(controllerContext.Controller, parametersArray) as Task;
+ Action cleanupAtEndExecute = () =>
+ {
+ // Cleanup code that's run in EndExecute, after we've waited on the task value.
+
+ if (taskCancelledTimer != null)
+ {
+ // Timer callback may still fire after Dispose is called.
+ taskCancelledTimer.Dispose();
+ }
+
+ if (tokenSource != null)
+ {
+ lock (tokenSource)
+ {
+ disposedTimer = true;
+ tokenSource.Dispose();
+ if (tokenSource.IsCancellationRequested)
+ {
+ // Give Timeout exceptions higher priority over other exceptions, mainly OperationCancelled exceptions
+ // that were signaled with out timeout token.
+ throw new TimeoutException();
+ }
+ }
+ }
+ };
+
+ TaskWrapperAsyncResult result = new TaskWrapperAsyncResult(taskUser, state, cleanupAtEndExecute);
+
+ // if user supplied a callback, invoke that when their task has finished running.
+ if (callback != null)
+ {
+ taskUser.Finally(() =>
+ {
+ callback(result);
+ });
+ }
+
+ return result;
+ }
+
+ public override object Execute(ControllerContext controllerContext, IDictionary<string, object> parameters)
+ {
+ string errorMessage = String.Format(CultureInfo.CurrentCulture, MvcResources.TaskAsyncActionDescriptor_CannotExecuteSynchronously,
+ ActionName);
+
+ throw new InvalidOperationException(errorMessage);
+ }
+
+ public override object EndExecute(IAsyncResult asyncResult)
+ {
+ TaskWrapperAsyncResult wrapperResult = (TaskWrapperAsyncResult)asyncResult;
+
+ // Throw an exception with the correct call stack
+ try
+ {
+ wrapperResult.Task.ThrowIfFaulted();
+ }
+ finally
+ {
+ if (wrapperResult.CleanupThunk != null)
+ {
+ wrapperResult.CleanupThunk();
+ }
+ }
+
+ // Extract the result of the task if there is a result
+ return _taskValueExtractors.GetOrAdd(TaskMethodInfo.ReturnType, CreateTaskValueExtractor)(wrapperResult.Task);
+ }
+
+ private static Func<object, object> CreateTaskValueExtractor(Type taskType)
+ {
+ // Task<T>?
+ if (taskType.IsGenericType && taskType.GetGenericTypeDefinition() == typeof(Task<>))
+ {
+ // lambda = arg => (object)(((Task<T>)arg).Result)
+ var arg = Expression.Parameter(typeof(object));
+ var castArg = Expression.Convert(arg, taskType);
+ var fieldAccess = Expression.Property(castArg, "Result");
+ var castResult = Expression.Convert(fieldAccess, typeof(object));
+ var lambda = Expression.Lambda<Func<object, object>>(castResult, arg);
+ return lambda.Compile();
+ }
+
+ // Any exceptions should be thrown before getting the task value so just return null.
+ return theTask =>
+ {
+ return null;
+ };
+ }
+
+ public override object[] GetCustomAttributes(bool inherit)
+ {
+ return ActionDescriptorHelper.GetCustomAttributes(TaskMethodInfo, inherit);
+ }
+
+ public override object[] GetCustomAttributes(Type attributeType, bool inherit)
+ {
+ return ActionDescriptorHelper.GetCustomAttributes(TaskMethodInfo, attributeType, inherit);
+ }
+
+ public override ParameterDescriptor[] GetParameters()
+ {
+ return ActionDescriptorHelper.GetParameters(this, TaskMethodInfo, ref _parametersCache);
+ }
+
+ public override ICollection<ActionSelector> GetSelectors()
+ {
+ return ActionDescriptorHelper.GetSelectors(TaskMethodInfo);
+ }
+
+ public override bool IsDefined(Type attributeType, bool inherit)
+ {
+ return ActionDescriptorHelper.IsDefined(TaskMethodInfo, attributeType, inherit);
+ }
+
+ public override IEnumerable<FilterAttribute> GetFilterAttributes(bool useCache)
+ {
+ if (useCache && GetType() == typeof(TaskAsyncActionDescriptor))
+ {
+ // Do not look at cache in types derived from this type because they might incorrectly implement GetCustomAttributes
+ return ReflectedAttributeCache.GetMethodFilterAttributes(TaskMethodInfo);
+ }
+ return base.GetFilterAttributes(useCache);
+ }
+ }
+}
diff --git a/src/System.Web.Mvc/Async/TaskWrapperAsyncResult.cs b/src/System.Web.Mvc/Async/TaskWrapperAsyncResult.cs
new file mode 100644
index 00000000..a9d31a94
--- /dev/null
+++ b/src/System.Web.Mvc/Async/TaskWrapperAsyncResult.cs
@@ -0,0 +1,43 @@
+using System.Threading;
+using System.Threading.Tasks;
+
+namespace System.Web.Mvc.Async
+{
+ /// <summary>
+ /// Wraps a <see cref="Task"/> class, optionally overriding the State object (since the Task Asynchronous Pattern doesn't normally use it).
+ /// Copied from System.Web.
+ /// </summary>
+ internal sealed class TaskWrapperAsyncResult : IAsyncResult
+ {
+ internal TaskWrapperAsyncResult(Task task, object asyncState, Action cleanupThunk = null)
+ {
+ Task = task;
+ AsyncState = asyncState;
+ CleanupThunk = cleanupThunk;
+ }
+
+ public object AsyncState { get; private set; }
+
+ public WaitHandle AsyncWaitHandle
+ {
+ get { return ((IAsyncResult)Task).AsyncWaitHandle; }
+ }
+
+ /// <summary>
+ /// Cleanup logic to run after Task is finished
+ /// </summary>
+ public Action CleanupThunk { get; private set; }
+
+ public bool CompletedSynchronously
+ {
+ get { return ((IAsyncResult)Task).CompletedSynchronously; }
+ }
+
+ public bool IsCompleted
+ {
+ get { return ((IAsyncResult)Task).IsCompleted; }
+ }
+
+ internal Task Task { get; private set; }
+ }
+}
diff --git a/src/System.Web.Mvc/Async/Trigger.cs b/src/System.Web.Mvc/Async/Trigger.cs
new file mode 100644
index 00000000..53efafbb
--- /dev/null
+++ b/src/System.Web.Mvc/Async/Trigger.cs
@@ -0,0 +1,20 @@
+namespace System.Web.Mvc.Async
+{
+ // Provides a trigger for the TriggerListener class.
+
+ internal sealed class Trigger
+ {
+ private readonly Action _fireAction;
+
+ // Constructor should only be called by TriggerListener.
+ internal Trigger(Action fireAction)
+ {
+ _fireAction = fireAction;
+ }
+
+ public void Fire()
+ {
+ _fireAction();
+ }
+ }
+}
diff --git a/src/System.Web.Mvc/Async/TriggerListener.cs b/src/System.Web.Mvc/Async/TriggerListener.cs
new file mode 100644
index 00000000..84b9a578
--- /dev/null
+++ b/src/System.Web.Mvc/Async/TriggerListener.cs
@@ -0,0 +1,65 @@
+using System.Threading;
+
+namespace System.Web.Mvc.Async
+{
+ // This class is used to wait for triggers and a continuation. When the continuation has been provded
+ // and all triggers have been fired, the continuation is called. Similar to WaitHandle.WaitAll().
+ // New instances of this type are initially in the inactive state; activation is enabled by a call
+ // to Activate().
+
+ // This class is thread-safe.
+
+ internal sealed class TriggerListener
+ {
+ private readonly Trigger _activateTrigger;
+ private readonly SingleEntryGate _continuationFiredGate = new SingleEntryGate();
+ private readonly Trigger _setContinuationTrigger;
+ private volatile Action _continuation;
+ private int _outstandingTriggers;
+
+ public TriggerListener()
+ {
+ _activateTrigger = CreateTrigger();
+ _setContinuationTrigger = CreateTrigger();
+ }
+
+ public void Activate()
+ {
+ _activateTrigger.Fire();
+ }
+
+ public Trigger CreateTrigger()
+ {
+ Interlocked.Increment(ref _outstandingTriggers);
+
+ SingleEntryGate triggerFiredGate = new SingleEntryGate();
+ return new Trigger(() =>
+ {
+ if (triggerFiredGate.TryEnter())
+ {
+ HandleTriggerFired();
+ }
+ });
+ }
+
+ private void HandleTriggerFired()
+ {
+ if (Interlocked.Decrement(ref _outstandingTriggers) == 0)
+ {
+ if (_continuationFiredGate.TryEnter())
+ {
+ _continuation();
+ }
+ }
+ }
+
+ public void SetContinuation(Action continuation)
+ {
+ if (continuation != null)
+ {
+ _continuation = continuation;
+ _setContinuationTrigger.Fire();
+ }
+ }
+ }
+}
diff --git a/src/System.Web.Mvc/AsyncController.cs b/src/System.Web.Mvc/AsyncController.cs
new file mode 100644
index 00000000..91e6c165
--- /dev/null
+++ b/src/System.Web.Mvc/AsyncController.cs
@@ -0,0 +1,10 @@
+namespace System.Web.Mvc
+{
+ // Controller now supports asynchronous operations.
+ // This class only exists
+ // a) for backwards compat for callers that derive from it,
+ // b) ActionMethodSelector can detect it to bind to ActionAsync/ActionCompleted patterns.
+ public abstract class AsyncController : Controller
+ {
+ }
+}
diff --git a/src/System.Web.Mvc/AsyncTimeoutAttribute.cs b/src/System.Web.Mvc/AsyncTimeoutAttribute.cs
new file mode 100644
index 00000000..4933269b
--- /dev/null
+++ b/src/System.Web.Mvc/AsyncTimeoutAttribute.cs
@@ -0,0 +1,41 @@
+using System.Diagnostics.CodeAnalysis;
+using System.Web.Mvc.Async;
+
+namespace System.Web.Mvc
+{
+ [SuppressMessage("Microsoft.Performance", "CA1813:AvoidUnsealedAttributes", Justification = "Unsealed so that subclassed types can set properties in the default constructor.")]
+ [AttributeUsage(AttributeTargets.Class | AttributeTargets.Method, Inherited = true, AllowMultiple = false)]
+ public class AsyncTimeoutAttribute : ActionFilterAttribute
+ {
+ // duration is specified in milliseconds
+ public AsyncTimeoutAttribute(int duration)
+ {
+ if (duration < -1)
+ {
+ throw Error.AsyncCommon_InvalidTimeout("duration");
+ }
+
+ Duration = duration;
+ }
+
+ public int Duration { get; private set; }
+
+ public override void OnActionExecuting(ActionExecutingContext filterContext)
+ {
+ if (filterContext == null)
+ {
+ throw new ArgumentNullException("filterContext");
+ }
+
+ IAsyncManagerContainer container = filterContext.Controller as IAsyncManagerContainer;
+ if (container == null)
+ {
+ throw Error.AsyncCommon_ControllerMustImplementIAsyncManagerContainer(filterContext.Controller.GetType());
+ }
+
+ container.AsyncManager.Timeout = Duration;
+
+ base.OnActionExecuting(filterContext);
+ }
+ }
+}
diff --git a/src/System.Web.Mvc/AuthorizationContext.cs b/src/System.Web.Mvc/AuthorizationContext.cs
new file mode 100644
index 00000000..a6546b5b
--- /dev/null
+++ b/src/System.Web.Mvc/AuthorizationContext.cs
@@ -0,0 +1,34 @@
+using System.Diagnostics.CodeAnalysis;
+
+namespace System.Web.Mvc
+{
+ public class AuthorizationContext : ControllerContext
+ {
+ // parameterless constructor used for mocking
+ public AuthorizationContext()
+ {
+ }
+
+ [Obsolete("The recommended alternative is the constructor AuthorizationContext(ControllerContext controllerContext, ActionDescriptor actionDescriptor).")]
+ public AuthorizationContext(ControllerContext controllerContext)
+ : base(controllerContext)
+ {
+ }
+
+ [SuppressMessage("Microsoft.Usage", "CA2214:DoNotCallOverridableMethodsInConstructors", Justification = "The virtual property setters are only to support mocking frameworks, in which case this constructor shouldn't be called anyway.")]
+ public AuthorizationContext(ControllerContext controllerContext, ActionDescriptor actionDescriptor)
+ : base(controllerContext)
+ {
+ if (actionDescriptor == null)
+ {
+ throw new ArgumentNullException("actionDescriptor");
+ }
+
+ ActionDescriptor = actionDescriptor;
+ }
+
+ public virtual ActionDescriptor ActionDescriptor { get; set; }
+
+ public ActionResult Result { get; set; }
+ }
+}
diff --git a/src/System.Web.Mvc/AuthorizeAttribute.cs b/src/System.Web.Mvc/AuthorizeAttribute.cs
new file mode 100644
index 00000000..434b94e1
--- /dev/null
+++ b/src/System.Web.Mvc/AuthorizeAttribute.cs
@@ -0,0 +1,152 @@
+using System.Diagnostics.CodeAnalysis;
+using System.Linq;
+using System.Security.Principal;
+using System.Web.Mvc.Properties;
+
+namespace System.Web.Mvc
+{
+ [SuppressMessage("Microsoft.Performance", "CA1813:AvoidUnsealedAttributes", Justification = "Unsealed so that subclassed types can set properties in the default constructor or override our behavior.")]
+ [AttributeUsage(AttributeTargets.Class | AttributeTargets.Method, Inherited = true, AllowMultiple = true)]
+ public class AuthorizeAttribute : FilterAttribute, IAuthorizationFilter
+ {
+ private readonly object _typeId = new object();
+
+ private string _roles;
+ private string[] _rolesSplit = new string[0];
+ private string _users;
+ private string[] _usersSplit = new string[0];
+
+ public string Roles
+ {
+ get { return _roles ?? String.Empty; }
+ set
+ {
+ _roles = value;
+ _rolesSplit = SplitString(value);
+ }
+ }
+
+ public override object TypeId
+ {
+ get { return _typeId; }
+ }
+
+ public string Users
+ {
+ get { return _users ?? String.Empty; }
+ set
+ {
+ _users = value;
+ _usersSplit = SplitString(value);
+ }
+ }
+
+ // This method must be thread-safe since it is called by the thread-safe OnCacheAuthorization() method.
+ protected virtual bool AuthorizeCore(HttpContextBase httpContext)
+ {
+ if (httpContext == null)
+ {
+ throw new ArgumentNullException("httpContext");
+ }
+
+ IPrincipal user = httpContext.User;
+ if (!user.Identity.IsAuthenticated)
+ {
+ return false;
+ }
+
+ if (_usersSplit.Length > 0 && !_usersSplit.Contains(user.Identity.Name, StringComparer.OrdinalIgnoreCase))
+ {
+ return false;
+ }
+
+ if (_rolesSplit.Length > 0 && !_rolesSplit.Any(user.IsInRole))
+ {
+ return false;
+ }
+
+ return true;
+ }
+
+ private void CacheValidateHandler(HttpContext context, object data, ref HttpValidationStatus validationStatus)
+ {
+ validationStatus = OnCacheAuthorization(new HttpContextWrapper(context));
+ }
+
+ public virtual void OnAuthorization(AuthorizationContext filterContext)
+ {
+ if (filterContext == null)
+ {
+ throw new ArgumentNullException("filterContext");
+ }
+
+ if (OutputCacheAttribute.IsChildActionCacheActive(filterContext))
+ {
+ // If a child action cache block is active, we need to fail immediately, even if authorization
+ // would have succeeded. The reason is that there's no way to hook a callback to rerun
+ // authorization before the fragment is served from the cache, so we can't guarantee that this
+ // filter will be re-run on subsequent requests.
+ throw new InvalidOperationException(MvcResources.AuthorizeAttribute_CannotUseWithinChildActionCache);
+ }
+
+ bool skipAuthorization = filterContext.ActionDescriptor.IsDefined(typeof(AllowAnonymousAttribute), inherit: true)
+ || filterContext.ActionDescriptor.ControllerDescriptor.IsDefined(typeof(AllowAnonymousAttribute), inherit: true);
+
+ if (skipAuthorization)
+ {
+ return;
+ }
+
+ if (AuthorizeCore(filterContext.HttpContext))
+ {
+ // ** IMPORTANT **
+ // Since we're performing authorization at the action level, the authorization code runs
+ // after the output caching module. In the worst case this could allow an authorized user
+ // to cause the page to be cached, then an unauthorized user would later be served the
+ // cached page. We work around this by telling proxies not to cache the sensitive page,
+ // then we hook our custom authorization code into the caching mechanism so that we have
+ // the final say on whether a page should be served from the cache.
+
+ HttpCachePolicyBase cachePolicy = filterContext.HttpContext.Response.Cache;
+ cachePolicy.SetProxyMaxAge(new TimeSpan(0));
+ cachePolicy.AddValidationCallback(CacheValidateHandler, null /* data */);
+ }
+ else
+ {
+ HandleUnauthorizedRequest(filterContext);
+ }
+ }
+
+ protected virtual void HandleUnauthorizedRequest(AuthorizationContext filterContext)
+ {
+ // Returns HTTP 401 - see comment in HttpUnauthorizedResult.cs.
+ filterContext.Result = new HttpUnauthorizedResult();
+ }
+
+ // This method must be thread-safe since it is called by the caching module.
+ protected virtual HttpValidationStatus OnCacheAuthorization(HttpContextBase httpContext)
+ {
+ if (httpContext == null)
+ {
+ throw new ArgumentNullException("httpContext");
+ }
+
+ bool isAuthorized = AuthorizeCore(httpContext);
+ return (isAuthorized) ? HttpValidationStatus.Valid : HttpValidationStatus.IgnoreThisRequest;
+ }
+
+ internal static string[] SplitString(string original)
+ {
+ if (String.IsNullOrEmpty(original))
+ {
+ return new string[0];
+ }
+
+ var split = from piece in original.Split(',')
+ let trimmed = piece.Trim()
+ where !String.IsNullOrEmpty(trimmed)
+ select trimmed;
+ return split.ToArray();
+ }
+ }
+}
diff --git a/src/System.Web.Mvc/BindAttribute.cs b/src/System.Web.Mvc/BindAttribute.cs
new file mode 100644
index 00000000..55165e42
--- /dev/null
+++ b/src/System.Web.Mvc/BindAttribute.cs
@@ -0,0 +1,50 @@
+using System.Linq;
+
+namespace System.Web.Mvc
+{
+ [AttributeUsage(AttributeTargets.Class | AttributeTargets.Parameter, AllowMultiple = false, Inherited = true)]
+ public sealed class BindAttribute : Attribute
+ {
+ private string _exclude;
+ private string[] _excludeSplit = new string[0];
+ private string _include;
+ private string[] _includeSplit = new string[0];
+
+ public string Exclude
+ {
+ get { return _exclude ?? String.Empty; }
+ set
+ {
+ _exclude = value;
+ _excludeSplit = AuthorizeAttribute.SplitString(value);
+ }
+ }
+
+ public string Include
+ {
+ get { return _include ?? String.Empty; }
+ set
+ {
+ _include = value;
+ _includeSplit = AuthorizeAttribute.SplitString(value);
+ }
+ }
+
+ public string Prefix { get; set; }
+
+ internal static bool IsPropertyAllowed(string propertyName, string[] includeProperties, string[] excludeProperties)
+ {
+ // We allow a property to be bound if its both in the include list AND not in the exclude list.
+ // An empty include list implies all properties are allowed.
+ // An empty exclude list implies no properties are disallowed.
+ bool includeProperty = (includeProperties == null) || (includeProperties.Length == 0) || includeProperties.Contains(propertyName, StringComparer.OrdinalIgnoreCase);
+ bool excludeProperty = (excludeProperties != null) && excludeProperties.Contains(propertyName, StringComparer.OrdinalIgnoreCase);
+ return includeProperty && !excludeProperty;
+ }
+
+ public bool IsPropertyAllowed(string propertyName)
+ {
+ return IsPropertyAllowed(propertyName, _includeSplit, _excludeSplit);
+ }
+ }
+}
diff --git a/src/System.Web.Mvc/BuildManagerCompiledView.cs b/src/System.Web.Mvc/BuildManagerCompiledView.cs
new file mode 100644
index 00000000..a7edef36
--- /dev/null
+++ b/src/System.Web.Mvc/BuildManagerCompiledView.cs
@@ -0,0 +1,85 @@
+using System.Globalization;
+using System.IO;
+using System.Web.Mvc.Properties;
+
+namespace System.Web.Mvc
+{
+ public abstract class BuildManagerCompiledView : IView
+ {
+ internal IViewPageActivator ViewPageActivator;
+ private IBuildManager _buildManager;
+ private ControllerContext _controllerContext;
+
+ protected BuildManagerCompiledView(ControllerContext controllerContext, string viewPath)
+ : this(controllerContext, viewPath, null)
+ {
+ }
+
+ protected BuildManagerCompiledView(ControllerContext controllerContext, string viewPath, IViewPageActivator viewPageActivator)
+ : this(controllerContext, viewPath, viewPageActivator, null)
+ {
+ }
+
+ internal BuildManagerCompiledView(ControllerContext controllerContext, string viewPath, IViewPageActivator viewPageActivator, IDependencyResolver dependencyResolver)
+ {
+ if (controllerContext == null)
+ {
+ throw new ArgumentNullException("controllerContext");
+ }
+ if (String.IsNullOrEmpty(viewPath))
+ {
+ throw new ArgumentException(MvcResources.Common_NullOrEmpty, "viewPath");
+ }
+
+ _controllerContext = controllerContext;
+
+ ViewPath = viewPath;
+
+ ViewPageActivator = viewPageActivator ?? new BuildManagerViewEngine.DefaultViewPageActivator(dependencyResolver);
+ }
+
+ internal IBuildManager BuildManager
+ {
+ get
+ {
+ if (_buildManager == null)
+ {
+ _buildManager = new BuildManagerWrapper();
+ }
+ return _buildManager;
+ }
+ set { _buildManager = value; }
+ }
+
+ public string ViewPath { get; protected set; }
+
+ public void Render(ViewContext viewContext, TextWriter writer)
+ {
+ if (viewContext == null)
+ {
+ throw new ArgumentNullException("viewContext");
+ }
+
+ object instance = null;
+
+ Type type = BuildManager.GetCompiledType(ViewPath);
+ if (type != null)
+ {
+ instance = ViewPageActivator.Create(_controllerContext, type);
+ }
+
+ if (instance == null)
+ {
+ throw new InvalidOperationException(
+ String.Format(
+ CultureInfo.CurrentCulture,
+ MvcResources.CshtmlView_ViewCouldNotBeCreated,
+ ViewPath));
+ }
+
+ RenderView(viewContext, writer, instance);
+ }
+
+ protected abstract void RenderView(ViewContext viewContext, TextWriter writer, object instance);
+ }
+}
diff --git a/src/System.Web.Mvc/BuildManagerViewEngine.cs b/src/System.Web.Mvc/BuildManagerViewEngine.cs
new file mode 100644
index 00000000..cbc4efc3
--- /dev/null
+++ b/src/System.Web.Mvc/BuildManagerViewEngine.cs
@@ -0,0 +1,92 @@
+namespace System.Web.Mvc
+{
+ public abstract class BuildManagerViewEngine : VirtualPathProviderViewEngine
+ {
+ private IBuildManager _buildManager;
+ private IViewPageActivator _viewPageActivator;
+ private IResolver<IViewPageActivator> _activatorResolver;
+
+ protected BuildManagerViewEngine()
+ : this(null, null, null)
+ {
+ }
+
+ protected BuildManagerViewEngine(IViewPageActivator viewPageActivator)
+ : this(viewPageActivator, null, null)
+ {
+ }
+
+ internal BuildManagerViewEngine(IViewPageActivator viewPageActivator, IResolver<IViewPageActivator> activatorResolver, IDependencyResolver dependencyResolver)
+ {
+ if (viewPageActivator != null)
+ {
+ _viewPageActivator = viewPageActivator;
+ }
+ else
+ {
+ _activatorResolver = activatorResolver ?? new SingleServiceResolver<IViewPageActivator>(
+ () => null,
+ new DefaultViewPageActivator(dependencyResolver),
+ "BuildManagerViewEngine constructor");
+ }
+ }
+
+ internal IBuildManager BuildManager
+ {
+ get
+ {
+ if (_buildManager == null)
+ {
+ _buildManager = new BuildManagerWrapper();
+ }
+ return _buildManager;
+ }
+ set { _buildManager = value; }
+ }
+
+ protected IViewPageActivator ViewPageActivator
+ {
+ get
+ {
+ if (_viewPageActivator != null)
+ {
+ return _viewPageActivator;
+ }
+ _viewPageActivator = _activatorResolver.Current;
+ return _viewPageActivator;
+ }
+ }
+
+ protected override bool FileExists(ControllerContext controllerContext, string virtualPath)
+ {
+ return BuildManager.FileExists(virtualPath);
+ }
+
+ internal class DefaultViewPageActivator : IViewPageActivator
+ {
+ private Func<IDependencyResolver> _resolverThunk;
+
+ public DefaultViewPageActivator()
+ : this(null)
+ {
+ }
+
+ public DefaultViewPageActivator(IDependencyResolver resolver)
+ {
+ if (resolver == null)
+ {
+ _resolverThunk = () => DependencyResolver.Current;
+ }
+ else
+ {
+ _resolverThunk = () => resolver;
+ }
+ }
+
+ public object Create(ControllerContext controllerContext, Type type)
+ {
+ return _resolverThunk().GetService(type) ?? Activator.CreateInstance(type);
+ }
+ }
+ }
+}
diff --git a/src/System.Web.Mvc/BuildManagerWrapper.cs b/src/System.Web.Mvc/BuildManagerWrapper.cs
new file mode 100644
index 00000000..13f2e94c
--- /dev/null
+++ b/src/System.Web.Mvc/BuildManagerWrapper.cs
@@ -0,0 +1,34 @@
+using System.Collections;
+using System.IO;
+using System.Web.Compilation;
+
+namespace System.Web.Mvc
+{
+ internal sealed class BuildManagerWrapper : IBuildManager
+ {
+ bool IBuildManager.FileExists(string virtualPath)
+ {
+ return BuildManager.GetObjectFactory(virtualPath, false) != null;
+ }
+
+ Type IBuildManager.GetCompiledType(string virtualPath)
+ {
+ return BuildManager.GetCompiledType(virtualPath);
+ }
+
+ ICollection IBuildManager.GetReferencedAssemblies()
+ {
+ return BuildManager.GetReferencedAssemblies();
+ }
+
+ Stream IBuildManager.ReadCachedFile(string fileName)
+ {
+ return BuildManager.ReadCachedFile(fileName);
+ }
+
+ Stream IBuildManager.CreateCachedFile(string fileName)
+ {
+ return BuildManager.CreateCachedFile(fileName);
+ }
+ }
+}
diff --git a/src/System.Web.Mvc/ByteArrayModelBinder.cs b/src/System.Web.Mvc/ByteArrayModelBinder.cs
new file mode 100644
index 00000000..df14ddb0
--- /dev/null
+++ b/src/System.Web.Mvc/ByteArrayModelBinder.cs
@@ -0,0 +1,34 @@
+namespace System.Web.Mvc
+{
+ public class ByteArrayModelBinder : IModelBinder
+ {
+ public virtual object BindModel(ControllerContext controllerContext, ModelBindingContext bindingContext)
+ {
+ if (bindingContext == null)
+ {
+ throw new ArgumentNullException("bindingContext");
+ }
+
+ ValueProviderResult valueResult = bindingContext.ValueProvider.GetValue(bindingContext.ModelName);
+
+ // case 1: there was no <input ... /> element containing this data
+ if (valueResult == null)
+ {
+ return null;
+ }
+
+ string value = valueResult.AttemptedValue;
+
+ // case 2: there was an <input ... /> element but it was left blank
+ if (String.IsNullOrEmpty(value))
+ {
+ return null;
+ }
+
+ // Future proofing. If the byte array is actually an instance of System.Data.Linq.Binary
+ // then we need to remove these quotes put in place by the ToString() method.
+ string realValue = value.Replace("\"", String.Empty);
+ return Convert.FromBase64String(realValue);
+ }
+ }
+}
diff --git a/src/System.Web.Mvc/CachedAssociatedMetadataProvider`1.cs b/src/System.Web.Mvc/CachedAssociatedMetadataProvider`1.cs
new file mode 100644
index 00000000..b0a00cb2
--- /dev/null
+++ b/src/System.Web.Mvc/CachedAssociatedMetadataProvider`1.cs
@@ -0,0 +1,94 @@
+using System.Collections.Concurrent;
+using System.Collections.Generic;
+using System.ComponentModel;
+using System.Runtime.Caching;
+
+namespace System.Web.Mvc
+{
+ public abstract class CachedAssociatedMetadataProvider<TModelMetadata> : AssociatedMetadataProvider
+ where TModelMetadata : ModelMetadata
+ {
+ private static ConcurrentDictionary<Type, string> _typeIds = new ConcurrentDictionary<Type, string>();
+ private string _cacheKeyPrefix;
+ private CacheItemPolicy _cacheItemPolicy = new CacheItemPolicy { SlidingExpiration = TimeSpan.FromMinutes(20) };
+ private ObjectCache _prototypeCache;
+
+ protected internal CacheItemPolicy CacheItemPolicy
+ {
+ get { return _cacheItemPolicy; }
+ set { _cacheItemPolicy = value; }
+ }
+
+ protected string CacheKeyPrefix
+ {
+ get
+ {
+ if (_cacheKeyPrefix == null)
+ {
+ _cacheKeyPrefix = "MetadataPrototypes::" + GetType().GUID.ToString("B");
+ }
+ return _cacheKeyPrefix;
+ }
+ }
+
+ protected internal ObjectCache PrototypeCache
+ {
+ get { return _prototypeCache ?? MemoryCache.Default; }
+ set { _prototypeCache = value; }
+ }
+
+ protected sealed override ModelMetadata CreateMetadata(IEnumerable<Attribute> attributes, Type containerType, Func<object> modelAccessor, Type modelType, string propertyName)
+ {
+ // If metadata is being created for a property then containerType != null && propertyName != null
+ // If metadata is being created for a type then containerType == null && propertyName == null, so we have to use modelType for the cache key.
+ Type typeForCache = containerType ?? modelType;
+ string cacheKey = GetCacheKey(typeForCache, propertyName);
+ TModelMetadata prototype = PrototypeCache.Get(cacheKey) as TModelMetadata;
+ if (prototype == null)
+ {
+ prototype = CreateMetadataPrototype(attributes, containerType, modelType, propertyName);
+ PrototypeCache.Add(cacheKey, prototype, CacheItemPolicy);
+ }
+
+ return CreateMetadataFromPrototype(prototype, modelAccessor);
+ }
+
+ // New override for creating the prototype metadata (without the accessor)
+ protected abstract TModelMetadata CreateMetadataPrototype(IEnumerable<Attribute> attributes, Type containerType, Type modelType, string propertyName);
+
+ // New override for applying the prototype + modelAccess to yield the final metadata
+ protected abstract TModelMetadata CreateMetadataFromPrototype(TModelMetadata prototype, Func<object> modelAccessor);
+
+ internal string GetCacheKey(Type type, string propertyName = null)
+ {
+ propertyName = propertyName ?? String.Empty;
+ return CacheKeyPrefix + GetTypeId(type) + propertyName;
+ }
+
+ public sealed override ModelMetadata GetMetadataForProperty(Func<object> modelAccessor, Type containerType, string propertyName)
+ {
+ return base.GetMetadataForProperty(modelAccessor, containerType, propertyName);
+ }
+
+ protected sealed override ModelMetadata GetMetadataForProperty(Func<object> modelAccessor, Type containerType, PropertyDescriptor propertyDescriptor)
+ {
+ return base.GetMetadataForProperty(modelAccessor, containerType, propertyDescriptor);
+ }
+
+ public sealed override IEnumerable<ModelMetadata> GetMetadataForProperties(object container, Type containerType)
+ {
+ return base.GetMetadataForProperties(container, containerType);
+ }
+
+ public sealed override ModelMetadata GetMetadataForType(Func<object> modelAccessor, Type modelType)
+ {
+ return base.GetMetadataForType(modelAccessor, modelType);
+ }
+
+ private static string GetTypeId(Type type)
+ {
+ // It's fine using a random Guid since we store the mapping for types to guids.
+ return _typeIds.GetOrAdd(type, _ => Guid.NewGuid().ToString("B"));
+ }
+ }
+}
diff --git a/src/System.Web.Mvc/CachedDataAnnotationsMetadataAttributes.cs b/src/System.Web.Mvc/CachedDataAnnotationsMetadataAttributes.cs
new file mode 100644
index 00000000..94a092ef
--- /dev/null
+++ b/src/System.Web.Mvc/CachedDataAnnotationsMetadataAttributes.cs
@@ -0,0 +1,54 @@
+using System.ComponentModel;
+using System.ComponentModel.DataAnnotations;
+using System.Linq;
+
+namespace System.Web.Mvc
+{
+ public class CachedDataAnnotationsMetadataAttributes
+ {
+ public CachedDataAnnotationsMetadataAttributes(Attribute[] attributes)
+ {
+ DataType = attributes.OfType<DataTypeAttribute>().FirstOrDefault();
+ Display = attributes.OfType<DisplayAttribute>().FirstOrDefault();
+ DisplayColumn = attributes.OfType<DisplayColumnAttribute>().FirstOrDefault();
+ DisplayFormat = attributes.OfType<DisplayFormatAttribute>().FirstOrDefault();
+ DisplayName = attributes.OfType<DisplayNameAttribute>().FirstOrDefault();
+ Editable = attributes.OfType<EditableAttribute>().FirstOrDefault();
+ HiddenInput = attributes.OfType<HiddenInputAttribute>().FirstOrDefault();
+ ReadOnly = attributes.OfType<ReadOnlyAttribute>().FirstOrDefault();
+ Required = attributes.OfType<RequiredAttribute>().FirstOrDefault();
+ ScaffoldColumn = attributes.OfType<ScaffoldColumnAttribute>().FirstOrDefault();
+
+ var uiHintAttributes = attributes.OfType<UIHintAttribute>();
+ UIHint = uiHintAttributes.FirstOrDefault(a => String.Equals(a.PresentationLayer, "MVC", StringComparison.OrdinalIgnoreCase))
+ ?? uiHintAttributes.FirstOrDefault(a => String.IsNullOrEmpty(a.PresentationLayer));
+
+ if (DisplayFormat == null && DataType != null)
+ {
+ DisplayFormat = DataType.DisplayFormat;
+ }
+ }
+
+ public DataTypeAttribute DataType { get; protected set; }
+
+ public DisplayAttribute Display { get; protected set; }
+
+ public DisplayColumnAttribute DisplayColumn { get; protected set; }
+
+ public DisplayFormatAttribute DisplayFormat { get; protected set; }
+
+ public DisplayNameAttribute DisplayName { get; protected set; }
+
+ public EditableAttribute Editable { get; protected set; }
+
+ public HiddenInputAttribute HiddenInput { get; protected set; }
+
+ public ReadOnlyAttribute ReadOnly { get; protected set; }
+
+ public RequiredAttribute Required { get; protected set; }
+
+ public ScaffoldColumnAttribute ScaffoldColumn { get; protected set; }
+
+ public UIHintAttribute UIHint { get; protected set; }
+ }
+}
diff --git a/src/System.Web.Mvc/CachedDataAnnotationsModelMetadata.cs b/src/System.Web.Mvc/CachedDataAnnotationsModelMetadata.cs
new file mode 100644
index 00000000..399b1481
--- /dev/null
+++ b/src/System.Web.Mvc/CachedDataAnnotationsModelMetadata.cs
@@ -0,0 +1,216 @@
+using System.Collections.Generic;
+using System.ComponentModel.DataAnnotations;
+using System.Globalization;
+using System.Linq;
+using System.Reflection;
+using System.Web.Mvc.Properties;
+
+namespace System.Web.Mvc
+{
+ public class CachedDataAnnotationsModelMetadata : CachedModelMetadata<CachedDataAnnotationsMetadataAttributes>
+ {
+ public CachedDataAnnotationsModelMetadata(CachedDataAnnotationsModelMetadata prototype, Func<object> modelAccessor)
+ : base(prototype, modelAccessor)
+ {
+ }
+
+ public CachedDataAnnotationsModelMetadata(CachedDataAnnotationsModelMetadataProvider provider, Type containerType, Type modelType, string propertyName, IEnumerable<Attribute> attributes)
+ : base(provider, containerType, modelType, propertyName, new CachedDataAnnotationsMetadataAttributes(attributes.ToArray()))
+ {
+ }
+
+ protected override bool ComputeConvertEmptyStringToNull()
+ {
+ return PrototypeCache.DisplayFormat != null
+ ? PrototypeCache.DisplayFormat.ConvertEmptyStringToNull
+ : base.ComputeConvertEmptyStringToNull();
+ }
+
+ protected override string ComputeDataTypeName()
+ {
+ if (PrototypeCache.DataType != null)
+ {
+ return PrototypeCache.DataType.ToDataTypeName();
+ }
+
+ if (PrototypeCache.DisplayFormat != null && !PrototypeCache.DisplayFormat.HtmlEncode)
+ {
+ return DataTypeUtil.HtmlTypeName;
+ }
+
+ return base.ComputeDataTypeName();
+ }
+
+ protected override string ComputeDescription()
+ {
+ return PrototypeCache.Display != null
+ ? PrototypeCache.Display.GetDescription()
+ : base.ComputeDescription();
+ }
+
+ protected override string ComputeDisplayFormatString()
+ {
+ return PrototypeCache.DisplayFormat != null
+ ? PrototypeCache.DisplayFormat.DataFormatString
+ : base.ComputeDisplayFormatString();
+ }
+
+ protected override string ComputeDisplayName()
+ {
+ string result = null;
+
+ if (PrototypeCache.Display != null)
+ {
+ result = PrototypeCache.Display.GetName();
+ }
+
+ if (result == null && PrototypeCache.DisplayName != null)
+ {
+ result = PrototypeCache.DisplayName.DisplayName;
+ }
+
+ return result ?? base.ComputeDisplayName();
+ }
+
+ protected override string ComputeEditFormatString()
+ {
+ if (PrototypeCache.DisplayFormat != null && PrototypeCache.DisplayFormat.ApplyFormatInEditMode)
+ {
+ return PrototypeCache.DisplayFormat.DataFormatString;
+ }
+
+ return base.ComputeEditFormatString();
+ }
+
+ protected override bool ComputeHideSurroundingHtml()
+ {
+ return PrototypeCache.HiddenInput != null
+ ? !PrototypeCache.HiddenInput.DisplayValue
+ : base.ComputeHideSurroundingHtml();
+ }
+
+ protected override bool ComputeIsReadOnly()
+ {
+ if (PrototypeCache.Editable != null)
+ {
+ return !PrototypeCache.Editable.AllowEdit;
+ }
+
+ if (PrototypeCache.ReadOnly != null)
+ {
+ return PrototypeCache.ReadOnly.IsReadOnly;
+ }
+
+ return base.ComputeIsReadOnly();
+ }
+
+ protected override bool ComputeIsRequired()
+ {
+ return PrototypeCache.Required != null
+ ? true
+ : base.ComputeIsRequired();
+ }
+
+ protected override string ComputeNullDisplayText()
+ {
+ return PrototypeCache.DisplayFormat != null
+ ? PrototypeCache.DisplayFormat.NullDisplayText
+ : base.ComputeNullDisplayText();
+ }
+
+ protected override int ComputeOrder()
+ {
+ int? result = null;
+
+ if (PrototypeCache.Display != null)
+ {
+ result = PrototypeCache.Display.GetOrder();
+ }
+
+ return result ?? base.ComputeOrder();
+ }
+
+ protected override string ComputeShortDisplayName()
+ {
+ return PrototypeCache.Display != null
+ ? PrototypeCache.Display.GetShortName()
+ : base.ComputeShortDisplayName();
+ }
+
+ protected override bool ComputeShowForDisplay()
+ {
+ return PrototypeCache.ScaffoldColumn != null
+ ? PrototypeCache.ScaffoldColumn.Scaffold
+ : base.ComputeShowForDisplay();
+ }
+
+ protected override bool ComputeShowForEdit()
+ {
+ return PrototypeCache.ScaffoldColumn != null
+ ? PrototypeCache.ScaffoldColumn.Scaffold
+ : base.ComputeShowForEdit();
+ }
+
+ protected override string ComputeSimpleDisplayText()
+ {
+ if (Model != null)
+ {
+ if (PrototypeCache.DisplayColumn != null && !String.IsNullOrEmpty(PrototypeCache.DisplayColumn.DisplayColumn))
+ {
+ PropertyInfo displayColumnProperty = ModelType.GetProperty(PrototypeCache.DisplayColumn.DisplayColumn, BindingFlags.Public | BindingFlags.IgnoreCase | BindingFlags.Instance);
+ ValidateDisplayColumnAttribute(PrototypeCache.DisplayColumn, displayColumnProperty, ModelType);
+
+ object simpleDisplayTextValue = displayColumnProperty.GetValue(Model, new object[0]);
+ if (simpleDisplayTextValue != null)
+ {
+ return simpleDisplayTextValue.ToString();
+ }
+ }
+ }
+
+ return base.ComputeSimpleDisplayText();
+ }
+
+ protected override string ComputeTemplateHint()
+ {
+ if (PrototypeCache.UIHint != null)
+ {
+ return PrototypeCache.UIHint.UIHint;
+ }
+
+ if (PrototypeCache.HiddenInput != null)
+ {
+ return "HiddenInput";
+ }
+
+ return base.ComputeTemplateHint();
+ }
+
+ protected override string ComputeWatermark()
+ {
+ return PrototypeCache.Display != null
+ ? PrototypeCache.Display.GetPrompt()
+ : base.ComputeWatermark();
+ }
+
+ private static void ValidateDisplayColumnAttribute(DisplayColumnAttribute displayColumnAttribute, PropertyInfo displayColumnProperty, Type modelType)
+ {
+ if (displayColumnProperty == null)
+ {
+ throw new InvalidOperationException(
+ String.Format(
+ CultureInfo.CurrentCulture,
+ MvcResources.DataAnnotationsModelMetadataProvider_UnknownProperty,
+ modelType.FullName, displayColumnAttribute.DisplayColumn));
+ }
+ if (displayColumnProperty.GetGetMethod() == null)
+ {
+ throw new InvalidOperationException(
+ String.Format(
+ CultureInfo.CurrentCulture,
+ MvcResources.DataAnnotationsModelMetadataProvider_UnreadableProperty,
+ modelType.FullName, displayColumnAttribute.DisplayColumn));
+ }
+ }
+ }
+}
diff --git a/src/System.Web.Mvc/CachedDataAnnotationsModelMetadataProvider.cs b/src/System.Web.Mvc/CachedDataAnnotationsModelMetadataProvider.cs
new file mode 100644
index 00000000..fab73542
--- /dev/null
+++ b/src/System.Web.Mvc/CachedDataAnnotationsModelMetadataProvider.cs
@@ -0,0 +1,17 @@
+using System.Collections.Generic;
+
+namespace System.Web.Mvc
+{
+ public class CachedDataAnnotationsModelMetadataProvider : CachedAssociatedMetadataProvider<CachedDataAnnotationsModelMetadata>
+ {
+ protected override CachedDataAnnotationsModelMetadata CreateMetadataPrototype(IEnumerable<Attribute> attributes, Type containerType, Type modelType, string propertyName)
+ {
+ return new CachedDataAnnotationsModelMetadata(this, containerType, modelType, propertyName, attributes);
+ }
+
+ protected override CachedDataAnnotationsModelMetadata CreateMetadataFromPrototype(CachedDataAnnotationsModelMetadata prototype, Func<object> modelAccessor)
+ {
+ return new CachedDataAnnotationsModelMetadata(prototype, modelAccessor);
+ }
+ }
+}
diff --git a/src/System.Web.Mvc/CachedModelMetadata`1.cs b/src/System.Web.Mvc/CachedModelMetadata`1.cs
new file mode 100644
index 00000000..bcc723e5
--- /dev/null
+++ b/src/System.Web.Mvc/CachedModelMetadata`1.cs
@@ -0,0 +1,415 @@
+namespace System.Web.Mvc
+{
+ // This class assumes that model metadata is expensive to create, and allows the user to
+ // stash a cache object that can be copied around as a prototype to make creation and
+ // computation quicker. It delegates the retrieval of values to getter methods, the results
+ // of which are cached on a per-metadata-instance basis.
+ //
+ // This allows flexible caching strategies: either caching the source of information across
+ // instances or caching of the actual information itself, depending on what the developer
+ // decides to put into the prototype cache.
+ public abstract class CachedModelMetadata<TPrototypeCache> : ModelMetadata
+ {
+ private bool _convertEmptyStringToNull;
+ private string _dataTypeName;
+ private string _description;
+ private string _displayFormatString;
+ private string _displayName;
+ private string _editFormatString;
+ private bool _hideSurroundingHtml;
+ private bool _isReadOnly;
+ private bool _isRequired;
+ private string _nullDisplayText;
+ private int _order;
+ private string _shortDisplayName;
+ private bool _showForDisplay;
+ private bool _showForEdit;
+ private string _templateHint;
+ private string _watermark;
+
+ private bool _convertEmptyStringToNullComputed;
+ private bool _dataTypeNameComputed;
+ private bool _descriptionComputed;
+ private bool _displayFormatStringComputed;
+ private bool _displayNameComputed;
+ private bool _editFormatStringComputed;
+ private bool _hideSurroundingHtmlComputed;
+ private bool _isReadOnlyComputed;
+ private bool _isRequiredComputed;
+ private bool _nullDisplayTextComputed;
+ private bool _orderComputed;
+ private bool _shortDisplayNameComputed;
+ private bool _showForDisplayComputed;
+ private bool _showForEditComputed;
+ private bool _templateHintComputed;
+ private bool _watermarkComputed;
+
+ // Constructor for creating real instances of the metadata class based on a prototype
+ protected CachedModelMetadata(CachedModelMetadata<TPrototypeCache> prototype, Func<object> modelAccessor)
+ : base(prototype.Provider, prototype.ContainerType, modelAccessor, prototype.ModelType, prototype.PropertyName)
+ {
+ PrototypeCache = prototype.PrototypeCache;
+ }
+
+ // Constructor for creating the prototype instances of the metadata class
+ protected CachedModelMetadata(CachedDataAnnotationsModelMetadataProvider provider, Type containerType, Type modelType, string propertyName, TPrototypeCache prototypeCache)
+ : base(provider, containerType, null /* modelAccessor */, modelType, propertyName)
+ {
+ PrototypeCache = prototypeCache;
+ }
+
+ public sealed override bool ConvertEmptyStringToNull
+ {
+ get
+ {
+ return CacheOrCompute(ComputeConvertEmptyStringToNull,
+ ref _convertEmptyStringToNull,
+ ref _convertEmptyStringToNullComputed);
+ }
+ set
+ {
+ _convertEmptyStringToNull = value;
+ _convertEmptyStringToNullComputed = true;
+ }
+ }
+
+ public sealed override string DataTypeName
+ {
+ get
+ {
+ return CacheOrCompute(ComputeDataTypeName,
+ ref _dataTypeName,
+ ref _dataTypeNameComputed);
+ }
+ set
+ {
+ _dataTypeName = value;
+ _dataTypeNameComputed = true;
+ }
+ }
+
+ public sealed override string Description
+ {
+ get
+ {
+ return CacheOrCompute(ComputeDescription,
+ ref _description,
+ ref _descriptionComputed);
+ }
+ set
+ {
+ _description = value;
+ _descriptionComputed = true;
+ }
+ }
+
+ public sealed override string DisplayFormatString
+ {
+ get
+ {
+ return CacheOrCompute(ComputeDisplayFormatString,
+ ref _displayFormatString,
+ ref _displayFormatStringComputed);
+ }
+ set
+ {
+ _displayFormatString = value;
+ _displayFormatStringComputed = true;
+ }
+ }
+
+ public sealed override string DisplayName
+ {
+ get
+ {
+ return CacheOrCompute(ComputeDisplayName,
+ ref _displayName,
+ ref _displayNameComputed);
+ }
+ set
+ {
+ _displayName = value;
+ _displayNameComputed = true;
+ }
+ }
+
+ public sealed override string EditFormatString
+ {
+ get
+ {
+ return CacheOrCompute(ComputeEditFormatString,
+ ref _editFormatString,
+ ref _editFormatStringComputed);
+ }
+ set
+ {
+ _editFormatString = value;
+ _editFormatStringComputed = true;
+ }
+ }
+
+ public sealed override bool HideSurroundingHtml
+ {
+ get
+ {
+ return CacheOrCompute(ComputeHideSurroundingHtml,
+ ref _hideSurroundingHtml,
+ ref _hideSurroundingHtmlComputed);
+ }
+ set
+ {
+ _hideSurroundingHtml = value;
+ _hideSurroundingHtmlComputed = true;
+ }
+ }
+
+ public sealed override bool IsReadOnly
+ {
+ get
+ {
+ return CacheOrCompute(ComputeIsReadOnly,
+ ref _isReadOnly,
+ ref _isReadOnlyComputed);
+ }
+ set
+ {
+ _isReadOnly = value;
+ _isReadOnlyComputed = true;
+ }
+ }
+
+ public sealed override bool IsRequired
+ {
+ get
+ {
+ return CacheOrCompute(ComputeIsRequired,
+ ref _isRequired,
+ ref _isRequiredComputed);
+ }
+ set
+ {
+ _isRequired = value;
+ _isRequiredComputed = true;
+ }
+ }
+
+ public sealed override string NullDisplayText
+ {
+ get
+ {
+ return CacheOrCompute(ComputeNullDisplayText,
+ ref _nullDisplayText,
+ ref _nullDisplayTextComputed);
+ }
+ set
+ {
+ _nullDisplayText = value;
+ _nullDisplayTextComputed = true;
+ }
+ }
+
+ public sealed override int Order
+ {
+ get
+ {
+ return CacheOrCompute(ComputeOrder,
+ ref _order,
+ ref _orderComputed);
+ }
+ set
+ {
+ _order = value;
+ _orderComputed = true;
+ }
+ }
+
+ protected TPrototypeCache PrototypeCache { get; set; }
+
+ public sealed override string ShortDisplayName
+ {
+ get
+ {
+ return CacheOrCompute(ComputeShortDisplayName,
+ ref _shortDisplayName,
+ ref _shortDisplayNameComputed);
+ }
+ set
+ {
+ _shortDisplayName = value;
+ _shortDisplayNameComputed = true;
+ }
+ }
+
+ public sealed override bool ShowForDisplay
+ {
+ get
+ {
+ return CacheOrCompute(ComputeShowForDisplay,
+ ref _showForDisplay,
+ ref _showForDisplayComputed);
+ }
+ set
+ {
+ _showForDisplay = value;
+ _showForDisplayComputed = true;
+ }
+ }
+
+ public sealed override bool ShowForEdit
+ {
+ get
+ {
+ return CacheOrCompute(ComputeShowForEdit,
+ ref _showForEdit,
+ ref _showForEditComputed);
+ }
+ set
+ {
+ _showForEdit = value;
+ _showForEditComputed = true;
+ }
+ }
+
+ public sealed override string SimpleDisplayText
+ {
+ get
+ {
+ // This is already cached in the base class with an appropriate override available
+ return base.SimpleDisplayText;
+ }
+ set { base.SimpleDisplayText = value; }
+ }
+
+ public sealed override string TemplateHint
+ {
+ get
+ {
+ return CacheOrCompute(ComputeTemplateHint,
+ ref _templateHint,
+ ref _templateHintComputed);
+ }
+ set
+ {
+ _templateHint = value;
+ _templateHintComputed = true;
+ }
+ }
+
+ public sealed override string Watermark
+ {
+ get
+ {
+ return CacheOrCompute(ComputeWatermark,
+ ref _watermark,
+ ref _watermarkComputed);
+ }
+ set
+ {
+ _watermark = value;
+ _watermarkComputed = true;
+ }
+ }
+
+ private static TResult CacheOrCompute<TResult>(Func<TResult> computeThunk, ref TResult value, ref bool computed)
+ {
+ if (!computed)
+ {
+ value = computeThunk();
+ computed = true;
+ }
+
+ return value;
+ }
+
+ protected virtual bool ComputeConvertEmptyStringToNull()
+ {
+ return base.ConvertEmptyStringToNull;
+ }
+
+ protected virtual string ComputeDataTypeName()
+ {
+ return base.DataTypeName;
+ }
+
+ protected virtual string ComputeDescription()
+ {
+ return base.Description;
+ }
+
+ protected virtual string ComputeDisplayFormatString()
+ {
+ return base.DisplayFormatString;
+ }
+
+ protected virtual string ComputeDisplayName()
+ {
+ return base.DisplayName;
+ }
+
+ protected virtual string ComputeEditFormatString()
+ {
+ return base.EditFormatString;
+ }
+
+ protected virtual bool ComputeHideSurroundingHtml()
+ {
+ return base.HideSurroundingHtml;
+ }
+
+ protected virtual bool ComputeIsReadOnly()
+ {
+ return base.IsReadOnly;
+ }
+
+ protected virtual bool ComputeIsRequired()
+ {
+ return base.IsRequired;
+ }
+
+ protected virtual string ComputeNullDisplayText()
+ {
+ return base.NullDisplayText;
+ }
+
+ protected virtual int ComputeOrder()
+ {
+ return base.Order;
+ }
+
+ protected virtual string ComputeShortDisplayName()
+ {
+ return base.ShortDisplayName;
+ }
+
+ protected virtual bool ComputeShowForDisplay()
+ {
+ return base.ShowForDisplay;
+ }
+
+ protected virtual bool ComputeShowForEdit()
+ {
+ return base.ShowForEdit;
+ }
+
+ protected virtual string ComputeSimpleDisplayText()
+ {
+ return base.GetSimpleDisplayText();
+ }
+
+ protected virtual string ComputeTemplateHint()
+ {
+ return base.TemplateHint;
+ }
+
+ protected virtual string ComputeWatermark()
+ {
+ return base.Watermark;
+ }
+
+ protected sealed override string GetSimpleDisplayText()
+ {
+ // Rename for consistency
+ return ComputeSimpleDisplayText();
+ }
+ }
+}
diff --git a/src/System.Web.Mvc/CancellationTokenModelBinder.cs b/src/System.Web.Mvc/CancellationTokenModelBinder.cs
new file mode 100644
index 00000000..24522296
--- /dev/null
+++ b/src/System.Web.Mvc/CancellationTokenModelBinder.cs
@@ -0,0 +1,12 @@
+using System.Threading;
+
+namespace System.Web.Mvc
+{
+ public class CancellationTokenModelBinder : IModelBinder
+ {
+ public object BindModel(ControllerContext controllerContext, ModelBindingContext bindingContext)
+ {
+ return default(CancellationToken);
+ }
+ }
+}
diff --git a/src/System.Web.Mvc/ChildActionOnlyAttribute.cs b/src/System.Web.Mvc/ChildActionOnlyAttribute.cs
new file mode 100644
index 00000000..16647f4a
--- /dev/null
+++ b/src/System.Web.Mvc/ChildActionOnlyAttribute.cs
@@ -0,0 +1,19 @@
+namespace System.Web.Mvc
+{
+ [AttributeUsage(AttributeTargets.Class | AttributeTargets.Method, AllowMultiple = false, Inherited = true)]
+ public sealed class ChildActionOnlyAttribute : FilterAttribute, IAuthorizationFilter
+ {
+ public void OnAuthorization(AuthorizationContext filterContext)
+ {
+ if (filterContext == null)
+ {
+ throw new ArgumentNullException("filterContext");
+ }
+
+ if (!filterContext.IsChildAction)
+ {
+ throw Error.ChildActionOnlyAttribute_MustBeInChildRequest(filterContext.ActionDescriptor);
+ }
+ }
+ }
+}
diff --git a/src/System.Web.Mvc/ChildActionValueProvider.cs b/src/System.Web.Mvc/ChildActionValueProvider.cs
new file mode 100644
index 00000000..ec1d3528
--- /dev/null
+++ b/src/System.Web.Mvc/ChildActionValueProvider.cs
@@ -0,0 +1,39 @@
+using System.Globalization;
+
+namespace System.Web.Mvc
+{
+ public sealed class ChildActionValueProvider : DictionaryValueProvider<object>
+ {
+ private static string _childActionValuesKey = Guid.NewGuid().ToString();
+
+ public ChildActionValueProvider(ControllerContext controllerContext)
+ : base(controllerContext.RouteData.Values, CultureInfo.InvariantCulture)
+ {
+ }
+
+ internal static string ChildActionValuesKey
+ {
+ get { return _childActionValuesKey; }
+ }
+
+ public override ValueProviderResult GetValue(string key)
+ {
+ if (key == null)
+ {
+ throw new ArgumentNullException("key");
+ }
+
+ ValueProviderResult explicitValues = base.GetValue(ChildActionValuesKey);
+ if (explicitValues != null)
+ {
+ DictionaryValueProvider<object> rawExplicitValues = explicitValues.RawValue as DictionaryValueProvider<object>;
+ if (rawExplicitValues != null)
+ {
+ return rawExplicitValues.GetValue(key);
+ }
+ }
+
+ return null;
+ }
+ }
+}
diff --git a/src/System.Web.Mvc/ChildActionValueProviderFactory.cs b/src/System.Web.Mvc/ChildActionValueProviderFactory.cs
new file mode 100644
index 00000000..1231a381
--- /dev/null
+++ b/src/System.Web.Mvc/ChildActionValueProviderFactory.cs
@@ -0,0 +1,15 @@
+namespace System.Web.Mvc
+{
+ public sealed class ChildActionValueProviderFactory : ValueProviderFactory
+ {
+ public override IValueProvider GetValueProvider(ControllerContext controllerContext)
+ {
+ if (controllerContext == null)
+ {
+ throw new ArgumentNullException("controllerContext");
+ }
+
+ return new ChildActionValueProvider(controllerContext);
+ }
+ }
+}
diff --git a/src/System.Web.Mvc/ClientDataTypeModelValidatorProvider.cs b/src/System.Web.Mvc/ClientDataTypeModelValidatorProvider.cs
new file mode 100644
index 00000000..080520b7
--- /dev/null
+++ b/src/System.Web.Mvc/ClientDataTypeModelValidatorProvider.cs
@@ -0,0 +1,159 @@
+using System.Collections.Generic;
+using System.Globalization;
+using System.Linq;
+using System.Web.Mvc.Properties;
+
+namespace System.Web.Mvc
+{
+ public class ClientDataTypeModelValidatorProvider : ModelValidatorProvider
+ {
+ private static readonly HashSet<Type> _numericTypes = new HashSet<Type>(new Type[]
+ {
+ typeof(byte), typeof(sbyte),
+ typeof(short), typeof(ushort),
+ typeof(int), typeof(uint),
+ typeof(long), typeof(ulong),
+ typeof(float), typeof(double), typeof(decimal)
+ });
+
+ private static string _resourceClassKey;
+
+ public static string ResourceClassKey
+ {
+ get { return _resourceClassKey ?? String.Empty; }
+ set { _resourceClassKey = value; }
+ }
+
+ public override IEnumerable<ModelValidator> GetValidators(ModelMetadata metadata, ControllerContext context)
+ {
+ if (metadata == null)
+ {
+ throw new ArgumentNullException("metadata");
+ }
+ if (context == null)
+ {
+ throw new ArgumentNullException("context");
+ }
+
+ return GetValidatorsImpl(metadata, context);
+ }
+
+ private static IEnumerable<ModelValidator> GetValidatorsImpl(ModelMetadata metadata, ControllerContext context)
+ {
+ Type type = metadata.ModelType;
+
+ if (IsDateTimeType(type))
+ {
+ yield return new DateModelValidator(metadata, context);
+ }
+
+ if (IsNumericType(type))
+ {
+ yield return new NumericModelValidator(metadata, context);
+ }
+ }
+
+ private static bool IsNumericType(Type type)
+ {
+ return _numericTypes.Contains(GetTypeToValidate(type));
+ }
+
+ private static bool IsDateTimeType(Type type)
+ {
+ return typeof(DateTime) == GetTypeToValidate(type);
+ }
+
+ private static Type GetTypeToValidate(Type type)
+ {
+ return Nullable.GetUnderlyingType(type) ?? type; // strip off the Nullable<>
+ }
+
+ // If the user specified a ResourceClassKey try to load the resource they specified.
+ // If the class key is invalid, an exception will be thrown.
+ // If the class key is valid but the resource is not found, it returns null, in which
+ // case it will fall back to the MVC default error message.
+ private static string GetUserResourceString(ControllerContext controllerContext, string resourceName)
+ {
+ string result = null;
+
+ if (!String.IsNullOrEmpty(ResourceClassKey) && (controllerContext != null) && (controllerContext.HttpContext != null))
+ {
+ result = controllerContext.HttpContext.GetGlobalResourceObject(ResourceClassKey, resourceName, CultureInfo.CurrentUICulture) as string;
+ }
+
+ return result;
+ }
+
+ private static string GetFieldMustBeNumericResource(ControllerContext controllerContext)
+ {
+ return GetUserResourceString(controllerContext, "FieldMustBeNumeric") ?? MvcResources.ClientDataTypeModelValidatorProvider_FieldMustBeNumeric;
+ }
+
+ private static string GetFieldMustBeDateResource(ControllerContext controllerContext)
+ {
+ return GetUserResourceString(controllerContext, "FieldMustBeDate") ?? MvcResources.ClientDataTypeModelValidatorProvider_FieldMustBeDate;
+ }
+
+ internal class ClientModelValidator : ModelValidator
+ {
+ private string _errorMessage;
+ private string _validationType;
+
+ public ClientModelValidator(ModelMetadata metadata, ControllerContext controllerContext, string validationType, string errorMessage)
+ : base(metadata, controllerContext)
+ {
+ if (String.IsNullOrEmpty(validationType))
+ {
+ throw new ArgumentException(MvcResources.Common_NullOrEmpty, "validationType");
+ }
+
+ if (String.IsNullOrEmpty(errorMessage))
+ {
+ throw new ArgumentException(MvcResources.Common_NullOrEmpty, "errorMessage");
+ }
+
+ _validationType = validationType;
+ _errorMessage = errorMessage;
+ }
+
+ public sealed override IEnumerable<ModelClientValidationRule> GetClientValidationRules()
+ {
+ ModelClientValidationRule rule = new ModelClientValidationRule()
+ {
+ ValidationType = _validationType,
+ ErrorMessage = FormatErrorMessage(Metadata.GetDisplayName())
+ };
+
+ return new ModelClientValidationRule[] { rule };
+ }
+
+ private string FormatErrorMessage(string displayName)
+ {
+ // use CurrentCulture since this message is intended for the site visitor
+ return String.Format(CultureInfo.CurrentCulture, _errorMessage, displayName);
+ }
+
+ public sealed override IEnumerable<ModelValidationResult> Validate(object container)
+ {
+ // this is not a server-side validator
+ return Enumerable.Empty<ModelValidationResult>();
+ }
+ }
+
+ internal sealed class DateModelValidator : ClientModelValidator
+ {
+ public DateModelValidator(ModelMetadata metadata, ControllerContext controllerContext)
+ : base(metadata, controllerContext, "date", GetFieldMustBeDateResource(controllerContext))
+ {
+ }
+ }
+
+ internal sealed class NumericModelValidator : ClientModelValidator
+ {
+ public NumericModelValidator(ModelMetadata metadata, ControllerContext controllerContext)
+ : base(metadata, controllerContext, "number", GetFieldMustBeNumericResource(controllerContext))
+ {
+ }
+ }
+ }
+}
diff --git a/src/System.Web.Mvc/CompareAttribute.cs b/src/System.Web.Mvc/CompareAttribute.cs
new file mode 100644
index 00000000..dc80ed05
--- /dev/null
+++ b/src/System.Web.Mvc/CompareAttribute.cs
@@ -0,0 +1,74 @@
+using System.Collections.Generic;
+using System.ComponentModel.DataAnnotations;
+using System.Diagnostics.CodeAnalysis;
+using System.Globalization;
+using System.Reflection;
+using System.Web.Mvc.Properties;
+
+namespace System.Web.Mvc
+{
+ [AttributeUsage(AttributeTargets.Property)]
+ [SuppressMessage("Microsoft.Performance", "CA1813:AvoidUnsealedAttributes", Justification = "This attribute is designed to be a base class for other attributes.")]
+ public class CompareAttribute : ValidationAttribute, IClientValidatable
+ {
+ public CompareAttribute(string otherProperty)
+ : base(MvcResources.CompareAttribute_MustMatch)
+ {
+ if (otherProperty == null)
+ {
+ throw new ArgumentNullException("otherProperty");
+ }
+ OtherProperty = otherProperty;
+ }
+
+ public string OtherProperty { get; private set; }
+
+ public string OtherPropertyDisplayName { get; internal set; }
+
+ public override string FormatErrorMessage(string name)
+ {
+ return String.Format(CultureInfo.CurrentCulture, ErrorMessageString, name, OtherPropertyDisplayName ?? OtherProperty);
+ }
+
+ protected override ValidationResult IsValid(object value, ValidationContext validationContext)
+ {
+ PropertyInfo otherPropertyInfo = validationContext.ObjectType.GetProperty(OtherProperty);
+ if (otherPropertyInfo == null)
+ {
+ return new ValidationResult(String.Format(CultureInfo.CurrentCulture, MvcResources.CompareAttribute_UnknownProperty, OtherProperty));
+ }
+
+ object otherPropertyValue = otherPropertyInfo.GetValue(validationContext.ObjectInstance, null);
+ if (!Equals(value, otherPropertyValue))
+ {
+ if (OtherPropertyDisplayName == null)
+ {
+ OtherPropertyDisplayName = ModelMetadataProviders.Current.GetMetadataForProperty(() => validationContext.ObjectInstance, validationContext.ObjectType, OtherProperty).GetDisplayName();
+ }
+ return new ValidationResult(FormatErrorMessage(validationContext.DisplayName));
+ }
+ return null;
+ }
+
+ public static string FormatPropertyForClientValidation(string property)
+ {
+ if (property == null)
+ {
+ throw new ArgumentException(MvcResources.Common_NullOrEmpty, "property");
+ }
+ return "*." + property;
+ }
+
+ public IEnumerable<ModelClientValidationRule> GetClientValidationRules(ModelMetadata metadata, ControllerContext context)
+ {
+ if (metadata.ContainerType != null)
+ {
+ if (OtherPropertyDisplayName == null)
+ {
+ OtherPropertyDisplayName = ModelMetadataProviders.Current.GetMetadataForProperty(() => metadata.Model, metadata.ContainerType, OtherProperty).GetDisplayName();
+ }
+ }
+ yield return new ModelClientValidationEqualToRule(FormatErrorMessage(metadata.GetDisplayName()), FormatPropertyForClientValidation(OtherProperty));
+ }
+ }
+}
diff --git a/src/System.Web.Mvc/ContentResult.cs b/src/System.Web.Mvc/ContentResult.cs
new file mode 100644
index 00000000..18812c36
--- /dev/null
+++ b/src/System.Web.Mvc/ContentResult.cs
@@ -0,0 +1,36 @@
+using System.Text;
+
+namespace System.Web.Mvc
+{
+ public class ContentResult : ActionResult
+ {
+ public string Content { get; set; }
+
+ public Encoding ContentEncoding { get; set; }
+
+ public string ContentType { get; set; }
+
+ public override void ExecuteResult(ControllerContext context)
+ {
+ if (context == null)
+ {
+ throw new ArgumentNullException("context");
+ }
+
+ HttpResponseBase response = context.HttpContext.Response;
+
+ if (!String.IsNullOrEmpty(ContentType))
+ {
+ response.ContentType = ContentType;
+ }
+ if (ContentEncoding != null)
+ {
+ response.ContentEncoding = ContentEncoding;
+ }
+ if (Content != null)
+ {
+ response.Write(Content);
+ }
+ }
+ }
+}
diff --git a/src/System.Web.Mvc/Controller.cs b/src/System.Web.Mvc/Controller.cs
new file mode 100644
index 00000000..e654c650
--- /dev/null
+++ b/src/System.Web.Mvc/Controller.cs
@@ -0,0 +1,920 @@
+using System.Diagnostics.CodeAnalysis;
+using System.Globalization;
+using System.IO;
+using System.Security.Principal;
+using System.Text;
+using System.Web.Mvc.Async;
+using System.Web.Mvc.Properties;
+using System.Web.Profile;
+using System.Web.Routing;
+
+namespace System.Web.Mvc
+{
+ [SuppressMessage("Microsoft.Maintainability", "CA1506:AvoidExcessiveClassCoupling", Justification = "Class complexity dictated by public surface area")]
+ public abstract class Controller : ControllerBase, IActionFilter, IAuthorizationFilter, IDisposable, IExceptionFilter, IResultFilter, IAsyncController, IAsyncManagerContainer
+ {
+ private static readonly object _executeTag = new object();
+ private static readonly object _executeCoreTag = new object();
+
+ private readonly AsyncManager _asyncManager = new AsyncManager();
+ private IActionInvoker _actionInvoker;
+ private ModelBinderDictionary _binders;
+ private RouteCollection _routeCollection;
+ private ITempDataProvider _tempDataProvider;
+ private ViewEngineCollection _viewEngineCollection;
+
+ private IDependencyResolver _resolver;
+
+ // By default, use the global resolver with caching.
+ // Or we can override to supply this instance with its own cache.
+ internal IDependencyResolver Resolver
+ {
+ get { return _resolver ?? DependencyResolver.CurrentCache; }
+ set { _resolver = value; }
+ }
+
+ public AsyncManager AsyncManager
+ {
+ get { return _asyncManager; }
+ }
+
+ /// <summary>
+ /// This is for backwards compat. MVC 4.0 starts allowing Controller to support asynchronous patterns.
+ /// This means ExecuteCore doesn't get called on derived classes. Derived classes can override this
+ /// flag and set to true if they still need ExecuteCore to be called.
+ /// </summary>
+ protected virtual bool DisableAsyncSupport
+ {
+ get { return false; }
+ }
+
+ public IActionInvoker ActionInvoker
+ {
+ get
+ {
+ if (_actionInvoker == null)
+ {
+ _actionInvoker = CreateActionInvoker();
+ }
+ return _actionInvoker;
+ }
+ set { _actionInvoker = value; }
+ }
+
+ [SuppressMessage("Microsoft.Usage", "CA2227:CollectionPropertiesShouldBeReadOnly", Justification = "Property is settable so that the dictionary can be provided for unit testing purposes.")]
+ protected internal ModelBinderDictionary Binders
+ {
+ get
+ {
+ if (_binders == null)
+ {
+ _binders = ModelBinders.Binders;
+ }
+ return _binders;
+ }
+ set { _binders = value; }
+ }
+
+ public HttpContextBase HttpContext
+ {
+ get { return ControllerContext == null ? null : ControllerContext.HttpContext; }
+ }
+
+ public ModelStateDictionary ModelState
+ {
+ get { return ViewData.ModelState; }
+ }
+
+ public ProfileBase Profile
+ {
+ get { return HttpContext == null ? null : HttpContext.Profile; }
+ }
+
+ public HttpRequestBase Request
+ {
+ get { return HttpContext == null ? null : HttpContext.Request; }
+ }
+
+ public HttpResponseBase Response
+ {
+ get { return HttpContext == null ? null : HttpContext.Response; }
+ }
+
+ internal RouteCollection RouteCollection
+ {
+ get
+ {
+ if (_routeCollection == null)
+ {
+ _routeCollection = RouteTable.Routes;
+ }
+ return _routeCollection;
+ }
+ set { _routeCollection = value; }
+ }
+
+ public RouteData RouteData
+ {
+ get { return ControllerContext == null ? null : ControllerContext.RouteData; }
+ }
+
+ public HttpServerUtilityBase Server
+ {
+ get { return HttpContext == null ? null : HttpContext.Server; }
+ }
+
+ public HttpSessionStateBase Session
+ {
+ get { return HttpContext == null ? null : HttpContext.Session; }
+ }
+
+ public ITempDataProvider TempDataProvider
+ {
+ get
+ {
+ if (_tempDataProvider == null)
+ {
+ _tempDataProvider = CreateTempDataProvider();
+ }
+ return _tempDataProvider;
+ }
+ set { _tempDataProvider = value; }
+ }
+
+ public UrlHelper Url { get; set; }
+
+ public IPrincipal User
+ {
+ get { return HttpContext == null ? null : HttpContext.User; }
+ }
+
+ [SuppressMessage("Microsoft.Usage", "CA2227:CollectionPropertiesShouldBeReadOnly", Justification = "This entire type is meant to be mutable.")]
+ public ViewEngineCollection ViewEngineCollection
+ {
+ get { return _viewEngineCollection ?? ViewEngines.Engines; }
+ set { _viewEngineCollection = value; }
+ }
+
+ [SuppressMessage("Microsoft.Naming", "CA1719:ParameterNamesShouldNotMatchMemberNames", MessageId = "0#", Justification = "'Content' refers to ContentResult type; 'content' refers to ContentResult.Content property.")]
+ protected internal ContentResult Content(string content)
+ {
+ return Content(content, null /* contentType */);
+ }
+
+ [SuppressMessage("Microsoft.Naming", "CA1719:ParameterNamesShouldNotMatchMemberNames", MessageId = "0#", Justification = "'Content' refers to ContentResult type; 'content' refers to ContentResult.Content property.")]
+ protected internal ContentResult Content(string content, string contentType)
+ {
+ return Content(content, contentType, null /* contentEncoding */);
+ }
+
+ [SuppressMessage("Microsoft.Naming", "CA1719:ParameterNamesShouldNotMatchMemberNames", MessageId = "0#", Justification = "'Content' refers to ContentResult type; 'content' refers to ContentResult.Content property.")]
+ protected internal virtual ContentResult Content(string content, string contentType, Encoding contentEncoding)
+ {
+ return new ContentResult
+ {
+ Content = content,
+ ContentType = contentType,
+ ContentEncoding = contentEncoding
+ };
+ }
+
+ protected virtual IActionInvoker CreateActionInvoker()
+ {
+ // Controller supports asynchronous operations by default.
+ return Resolver.GetService<IAsyncActionInvoker>() ?? Resolver.GetService<IActionInvoker>() ?? new AsyncControllerActionInvoker();
+ }
+
+ protected virtual ITempDataProvider CreateTempDataProvider()
+ {
+ return Resolver.GetService<ITempDataProvider>() ?? new SessionStateTempDataProvider();
+ }
+
+ // The default invoker will never match methods defined on the Controller type, so
+ // the Dispose() method is not web-callable. However, in general, since implicitly-
+ // implemented interface methods are public, they are web-callable unless decorated with
+ // [NonAction].
+ public void Dispose()
+ {
+ Dispose(true /* disposing */);
+ GC.SuppressFinalize(this);
+ }
+
+ protected virtual void Dispose(bool disposing)
+ {
+ }
+
+ protected override void ExecuteCore()
+ {
+ // If code in this method needs to be updated, please also check the BeginExecuteCore() and
+ // EndExecuteCore() methods of AsyncController to see if that code also must be updated.
+
+ PossiblyLoadTempData();
+ try
+ {
+ string actionName = RouteData.GetRequiredString("action");
+ if (!ActionInvoker.InvokeAction(ControllerContext, actionName))
+ {
+ HandleUnknownAction(actionName);
+ }
+ }
+ finally
+ {
+ PossiblySaveTempData();
+ }
+ }
+
+ protected internal FileContentResult File(byte[] fileContents, string contentType)
+ {
+ return File(fileContents, contentType, null /* fileDownloadName */);
+ }
+
+ protected internal virtual FileContentResult File(byte[] fileContents, string contentType, string fileDownloadName)
+ {
+ return new FileContentResult(fileContents, contentType) { FileDownloadName = fileDownloadName };
+ }
+
+ protected internal FileStreamResult File(Stream fileStream, string contentType)
+ {
+ return File(fileStream, contentType, null /* fileDownloadName */);
+ }
+
+ protected internal virtual FileStreamResult File(Stream fileStream, string contentType, string fileDownloadName)
+ {
+ return new FileStreamResult(fileStream, contentType) { FileDownloadName = fileDownloadName };
+ }
+
+ protected internal FilePathResult File(string fileName, string contentType)
+ {
+ return File(fileName, contentType, null /* fileDownloadName */);
+ }
+
+ protected internal virtual FilePathResult File(string fileName, string contentType, string fileDownloadName)
+ {
+ return new FilePathResult(fileName, contentType) { FileDownloadName = fileDownloadName };
+ }
+
+ protected virtual void HandleUnknownAction(string actionName)
+ {
+ throw new HttpException(404, String.Format(CultureInfo.CurrentCulture,
+ MvcResources.Controller_UnknownAction, actionName, GetType().FullName));
+ }
+
+ protected internal HttpNotFoundResult HttpNotFound()
+ {
+ return HttpNotFound(null);
+ }
+
+ protected internal virtual HttpNotFoundResult HttpNotFound(string statusDescription)
+ {
+ return new HttpNotFoundResult(statusDescription);
+ }
+
+ protected internal virtual JavaScriptResult JavaScript(string script)
+ {
+ return new JavaScriptResult { Script = script };
+ }
+
+ protected internal JsonResult Json(object data)
+ {
+ return Json(data, null /* contentType */, null /* contentEncoding */, JsonRequestBehavior.DenyGet);
+ }
+
+ protected internal JsonResult Json(object data, string contentType)
+ {
+ return Json(data, contentType, null /* contentEncoding */, JsonRequestBehavior.DenyGet);
+ }
+
+ protected internal virtual JsonResult Json(object data, string contentType, Encoding contentEncoding)
+ {
+ return Json(data, contentType, contentEncoding, JsonRequestBehavior.DenyGet);
+ }
+
+ protected internal JsonResult Json(object data, JsonRequestBehavior behavior)
+ {
+ return Json(data, null /* contentType */, null /* contentEncoding */, behavior);
+ }
+
+ protected internal JsonResult Json(object data, string contentType, JsonRequestBehavior behavior)
+ {
+ return Json(data, contentType, null /* contentEncoding */, behavior);
+ }
+
+ protected internal virtual JsonResult Json(object data, string contentType, Encoding contentEncoding, JsonRequestBehavior behavior)
+ {
+ return new JsonResult
+ {
+ Data = data,
+ ContentType = contentType,
+ ContentEncoding = contentEncoding,
+ JsonRequestBehavior = behavior
+ };
+ }
+
+ protected override void Initialize(RequestContext requestContext)
+ {
+ base.Initialize(requestContext);
+ Url = new UrlHelper(requestContext);
+ }
+
+ protected virtual void OnActionExecuting(ActionExecutingContext filterContext)
+ {
+ }
+
+ protected virtual void OnActionExecuted(ActionExecutedContext filterContext)
+ {
+ }
+
+ protected virtual void OnAuthorization(AuthorizationContext filterContext)
+ {
+ }
+
+ protected virtual void OnException(ExceptionContext filterContext)
+ {
+ }
+
+ protected virtual void OnResultExecuted(ResultExecutedContext filterContext)
+ {
+ }
+
+ protected virtual void OnResultExecuting(ResultExecutingContext filterContext)
+ {
+ }
+
+ protected internal PartialViewResult PartialView()
+ {
+ return PartialView(null /* viewName */, null /* model */);
+ }
+
+ protected internal PartialViewResult PartialView(object model)
+ {
+ return PartialView(null /* viewName */, model);
+ }
+
+ protected internal PartialViewResult PartialView(string viewName)
+ {
+ return PartialView(viewName, null /* model */);
+ }
+
+ protected internal virtual PartialViewResult PartialView(string viewName, object model)
+ {
+ if (model != null)
+ {
+ ViewData.Model = model;
+ }
+
+ return new PartialViewResult
+ {
+ ViewName = viewName,
+ ViewData = ViewData,
+ TempData = TempData,
+ ViewEngineCollection = ViewEngineCollection
+ };
+ }
+
+ internal void PossiblyLoadTempData()
+ {
+ if (!ControllerContext.IsChildAction)
+ {
+ TempData.Load(ControllerContext, TempDataProvider);
+ }
+ }
+
+ internal void PossiblySaveTempData()
+ {
+ if (!ControllerContext.IsChildAction)
+ {
+ TempData.Save(ControllerContext, TempDataProvider);
+ }
+ }
+
+ [SuppressMessage("Microsoft.Design", "CA1054:UriParametersShouldNotBeStrings", MessageId = "0#", Justification = "Response.Redirect() takes its URI as a string parameter.")]
+ protected internal virtual RedirectResult Redirect(string url)
+ {
+ if (String.IsNullOrEmpty(url))
+ {
+ throw new ArgumentException(MvcResources.Common_NullOrEmpty, "url");
+ }
+
+ return new RedirectResult(url);
+ }
+
+ [SuppressMessage("Microsoft.Design", "CA1054:UriParametersShouldNotBeStrings", MessageId = "0#", Justification = "Response.RedirectPermanent() takes its URI as a string parameter.")]
+ protected internal virtual RedirectResult RedirectPermanent(string url)
+ {
+ if (String.IsNullOrEmpty(url))
+ {
+ throw new ArgumentException(MvcResources.Common_NullOrEmpty, "url");
+ }
+
+ return new RedirectResult(url, permanent: true);
+ }
+
+ protected internal RedirectToRouteResult RedirectToAction(string actionName)
+ {
+ return RedirectToAction(actionName, (RouteValueDictionary)null);
+ }
+
+ protected internal RedirectToRouteResult RedirectToAction(string actionName, object routeValues)
+ {
+ return RedirectToAction(actionName, new RouteValueDictionary(routeValues));
+ }
+
+ protected internal RedirectToRouteResult RedirectToAction(string actionName, RouteValueDictionary routeValues)
+ {
+ return RedirectToAction(actionName, null /* controllerName */, routeValues);
+ }
+
+ protected internal RedirectToRouteResult RedirectToAction(string actionName, string controllerName)
+ {
+ return RedirectToAction(actionName, controllerName, (RouteValueDictionary)null);
+ }
+
+ protected internal RedirectToRouteResult RedirectToAction(string actionName, string controllerName, object routeValues)
+ {
+ return RedirectToAction(actionName, controllerName, new RouteValueDictionary(routeValues));
+ }
+
+ protected internal virtual RedirectToRouteResult RedirectToAction(string actionName, string controllerName, RouteValueDictionary routeValues)
+ {
+ RouteValueDictionary mergedRouteValues;
+
+ if (RouteData == null)
+ {
+ mergedRouteValues = RouteValuesHelpers.MergeRouteValues(actionName, controllerName, null, routeValues, includeImplicitMvcValues: true);
+ }
+ else
+ {
+ mergedRouteValues = RouteValuesHelpers.MergeRouteValues(actionName, controllerName, RouteData.Values, routeValues, includeImplicitMvcValues: true);
+ }
+
+ return new RedirectToRouteResult(mergedRouteValues);
+ }
+
+ protected internal RedirectToRouteResult RedirectToActionPermanent(string actionName)
+ {
+ return RedirectToActionPermanent(actionName, (RouteValueDictionary)null);
+ }
+
+ protected internal RedirectToRouteResult RedirectToActionPermanent(string actionName, object routeValues)
+ {
+ return RedirectToActionPermanent(actionName, new RouteValueDictionary(routeValues));
+ }
+
+ protected internal RedirectToRouteResult RedirectToActionPermanent(string actionName, RouteValueDictionary routeValues)
+ {
+ return RedirectToActionPermanent(actionName, null /* controllerName */, routeValues);
+ }
+
+ protected internal RedirectToRouteResult RedirectToActionPermanent(string actionName, string controllerName)
+ {
+ return RedirectToActionPermanent(actionName, controllerName, (RouteValueDictionary)null);
+ }
+
+ protected internal RedirectToRouteResult RedirectToActionPermanent(string actionName, string controllerName, object routeValues)
+ {
+ return RedirectToActionPermanent(actionName, controllerName, new RouteValueDictionary(routeValues));
+ }
+
+ protected internal virtual RedirectToRouteResult RedirectToActionPermanent(string actionName, string controllerName, RouteValueDictionary routeValues)
+ {
+ RouteValueDictionary implicitRouteValues = (RouteData != null) ? RouteData.Values : null;
+
+ RouteValueDictionary mergedRouteValues =
+ RouteValuesHelpers.MergeRouteValues(actionName, controllerName, implicitRouteValues, routeValues, includeImplicitMvcValues: true);
+
+ return new RedirectToRouteResult(null, mergedRouteValues, permanent: true);
+ }
+
+ protected internal RedirectToRouteResult RedirectToRoute(object routeValues)
+ {
+ return RedirectToRoute(new RouteValueDictionary(routeValues));
+ }
+
+ protected internal RedirectToRouteResult RedirectToRoute(RouteValueDictionary routeValues)
+ {
+ return RedirectToRoute(null /* routeName */, routeValues);
+ }
+
+ protected internal RedirectToRouteResult RedirectToRoute(string routeName)
+ {
+ return RedirectToRoute(routeName, (RouteValueDictionary)null);
+ }
+
+ protected internal RedirectToRouteResult RedirectToRoute(string routeName, object routeValues)
+ {
+ return RedirectToRoute(routeName, new RouteValueDictionary(routeValues));
+ }
+
+ protected internal virtual RedirectToRouteResult RedirectToRoute(string routeName, RouteValueDictionary routeValues)
+ {
+ return new RedirectToRouteResult(routeName, RouteValuesHelpers.GetRouteValues(routeValues));
+ }
+
+ protected internal RedirectToRouteResult RedirectToRoutePermanent(object routeValues)
+ {
+ return RedirectToRoutePermanent(new RouteValueDictionary(routeValues));
+ }
+
+ protected internal RedirectToRouteResult RedirectToRoutePermanent(RouteValueDictionary routeValues)
+ {
+ return RedirectToRoutePermanent(null /* routeName */, routeValues);
+ }
+
+ protected internal RedirectToRouteResult RedirectToRoutePermanent(string routeName)
+ {
+ return RedirectToRoutePermanent(routeName, (RouteValueDictionary)null);
+ }
+
+ protected internal RedirectToRouteResult RedirectToRoutePermanent(string routeName, object routeValues)
+ {
+ return RedirectToRoutePermanent(routeName, new RouteValueDictionary(routeValues));
+ }
+
+ protected internal virtual RedirectToRouteResult RedirectToRoutePermanent(string routeName, RouteValueDictionary routeValues)
+ {
+ return new RedirectToRouteResult(routeName, RouteValuesHelpers.GetRouteValues(routeValues), permanent: true);
+ }
+
+ protected internal bool TryUpdateModel<TModel>(TModel model) where TModel : class
+ {
+ return TryUpdateModel(model, null, null, null, ValueProvider);
+ }
+
+ protected internal bool TryUpdateModel<TModel>(TModel model, string prefix) where TModel : class
+ {
+ return TryUpdateModel(model, prefix, null, null, ValueProvider);
+ }
+
+ protected internal bool TryUpdateModel<TModel>(TModel model, string[] includeProperties) where TModel : class
+ {
+ return TryUpdateModel(model, null, includeProperties, null, ValueProvider);
+ }
+
+ protected internal bool TryUpdateModel<TModel>(TModel model, string prefix, string[] includeProperties) where TModel : class
+ {
+ return TryUpdateModel(model, prefix, includeProperties, null, ValueProvider);
+ }
+
+ protected internal bool TryUpdateModel<TModel>(TModel model, string prefix, string[] includeProperties, string[] excludeProperties) where TModel : class
+ {
+ return TryUpdateModel(model, prefix, includeProperties, excludeProperties, ValueProvider);
+ }
+
+ protected internal bool TryUpdateModel<TModel>(TModel model, IValueProvider valueProvider) where TModel : class
+ {
+ return TryUpdateModel(model, null, null, null, valueProvider);
+ }
+
+ protected internal bool TryUpdateModel<TModel>(TModel model, string prefix, IValueProvider valueProvider) where TModel : class
+ {
+ return TryUpdateModel(model, prefix, null, null, valueProvider);
+ }
+
+ protected internal bool TryUpdateModel<TModel>(TModel model, string[] includeProperties, IValueProvider valueProvider) where TModel : class
+ {
+ return TryUpdateModel(model, null, includeProperties, null, valueProvider);
+ }
+
+ protected internal bool TryUpdateModel<TModel>(TModel model, string prefix, string[] includeProperties, IValueProvider valueProvider) where TModel : class
+ {
+ return TryUpdateModel(model, prefix, includeProperties, null, valueProvider);
+ }
+
+ protected internal bool TryUpdateModel<TModel>(TModel model, string prefix, string[] includeProperties, string[] excludeProperties, IValueProvider valueProvider) where TModel : class
+ {
+ if (model == null)
+ {
+ throw new ArgumentNullException("model");
+ }
+ if (valueProvider == null)
+ {
+ throw new ArgumentNullException("valueProvider");
+ }
+
+ Predicate<string> propertyFilter = propertyName => BindAttribute.IsPropertyAllowed(propertyName, includeProperties, excludeProperties);
+ IModelBinder binder = Binders.GetBinder(typeof(TModel));
+
+ ModelBindingContext bindingContext = new ModelBindingContext()
+ {
+ ModelMetadata = ModelMetadataProviders.Current.GetMetadataForType(() => model, typeof(TModel)),
+ ModelName = prefix,
+ ModelState = ModelState,
+ PropertyFilter = propertyFilter,
+ ValueProvider = valueProvider
+ };
+ binder.BindModel(ControllerContext, bindingContext);
+ return ModelState.IsValid;
+ }
+
+ protected internal bool TryValidateModel(object model)
+ {
+ return TryValidateModel(model, null /* prefix */);
+ }
+
+ protected internal bool TryValidateModel(object model, string prefix)
+ {
+ if (model == null)
+ {
+ throw new ArgumentNullException("model");
+ }
+
+ ModelMetadata metadata = ModelMetadataProviders.Current.GetMetadataForType(() => model, model.GetType());
+
+ foreach (ModelValidationResult validationResult in ModelValidator.GetModelValidator(metadata, ControllerContext).Validate(null))
+ {
+ ModelState.AddModelError(DefaultModelBinder.CreateSubPropertyName(prefix, validationResult.MemberName), validationResult.Message);
+ }
+
+ return ModelState.IsValid;
+ }
+
+ protected internal void UpdateModel<TModel>(TModel model) where TModel : class
+ {
+ UpdateModel(model, null, null, null, ValueProvider);
+ }
+
+ protected internal void UpdateModel<TModel>(TModel model, string prefix) where TModel : class
+ {
+ UpdateModel(model, prefix, null, null, ValueProvider);
+ }
+
+ protected internal void UpdateModel<TModel>(TModel model, string[] includeProperties) where TModel : class
+ {
+ UpdateModel(model, null, includeProperties, null, ValueProvider);
+ }
+
+ protected internal void UpdateModel<TModel>(TModel model, string prefix, string[] includeProperties) where TModel : class
+ {
+ UpdateModel(model, prefix, includeProperties, null, ValueProvider);
+ }
+
+ protected internal void UpdateModel<TModel>(TModel model, string prefix, string[] includeProperties, string[] excludeProperties) where TModel : class
+ {
+ UpdateModel(model, prefix, includeProperties, excludeProperties, ValueProvider);
+ }
+
+ protected internal void UpdateModel<TModel>(TModel model, IValueProvider valueProvider) where TModel : class
+ {
+ UpdateModel(model, null, null, null, valueProvider);
+ }
+
+ protected internal void UpdateModel<TModel>(TModel model, string prefix, IValueProvider valueProvider) where TModel : class
+ {
+ UpdateModel(model, prefix, null, null, valueProvider);
+ }
+
+ protected internal void UpdateModel<TModel>(TModel model, string[] includeProperties, IValueProvider valueProvider) where TModel : class
+ {
+ UpdateModel(model, null, includeProperties, null, valueProvider);
+ }
+
+ protected internal void UpdateModel<TModel>(TModel model, string prefix, string[] includeProperties, IValueProvider valueProvider) where TModel : class
+ {
+ UpdateModel(model, prefix, includeProperties, null, valueProvider);
+ }
+
+ protected internal void UpdateModel<TModel>(TModel model, string prefix, string[] includeProperties, string[] excludeProperties, IValueProvider valueProvider) where TModel : class
+ {
+ bool success = TryUpdateModel(model, prefix, includeProperties, excludeProperties, valueProvider);
+ if (!success)
+ {
+ string message = String.Format(CultureInfo.CurrentCulture, MvcResources.Controller_UpdateModel_UpdateUnsuccessful,
+ typeof(TModel).FullName);
+ throw new InvalidOperationException(message);
+ }
+ }
+
+ protected internal void ValidateModel(object model)
+ {
+ ValidateModel(model, null /* prefix */);
+ }
+
+ protected internal void ValidateModel(object model, string prefix)
+ {
+ if (!TryValidateModel(model, prefix))
+ {
+ throw new InvalidOperationException(
+ String.Format(
+ CultureInfo.CurrentCulture,
+ MvcResources.Controller_Validate_ValidationFailed,
+ model.GetType().FullName));
+ }
+ }
+
+ protected internal ViewResult View()
+ {
+ return View(viewName: null, masterName: null, model: null);
+ }
+
+ protected internal ViewResult View(object model)
+ {
+ return View(null /* viewName */, null /* masterName */, model);
+ }
+
+ protected internal ViewResult View(string viewName)
+ {
+ return View(viewName, masterName: null, model: null);
+ }
+
+ protected internal ViewResult View(string viewName, string masterName)
+ {
+ return View(viewName, masterName, null /* model */);
+ }
+
+ protected internal ViewResult View(string viewName, object model)
+ {
+ return View(viewName, null /* masterName */, model);
+ }
+
+ protected internal virtual ViewResult View(string viewName, string masterName, object model)
+ {
+ if (model != null)
+ {
+ ViewData.Model = model;
+ }
+
+ return new ViewResult
+ {
+ ViewName = viewName,
+ MasterName = masterName,
+ ViewData = ViewData,
+ TempData = TempData,
+ ViewEngineCollection = ViewEngineCollection
+ };
+ }
+
+ [SuppressMessage("Microsoft.Naming", "CA1719:ParameterNamesShouldNotMatchMemberNames", MessageId = "0#", Justification = "The method name 'View' is a convenient shorthand for 'CreateViewResult'.")]
+ protected internal ViewResult View(IView view)
+ {
+ return View(view, null /* model */);
+ }
+
+ [SuppressMessage("Microsoft.Naming", "CA1719:ParameterNamesShouldNotMatchMemberNames", MessageId = "0#", Justification = "The method name 'View' is a convenient shorthand for 'CreateViewResult'.")]
+ protected internal virtual ViewResult View(IView view, object model)
+ {
+ if (model != null)
+ {
+ ViewData.Model = model;
+ }
+
+ return new ViewResult
+ {
+ View = view,
+ ViewData = ViewData,
+ TempData = TempData
+ };
+ }
+
+ IAsyncResult IAsyncController.BeginExecute(RequestContext requestContext, AsyncCallback callback, object state)
+ {
+ return BeginExecute(requestContext, callback, state);
+ }
+
+ void IAsyncController.EndExecute(IAsyncResult asyncResult)
+ {
+ EndExecute(asyncResult);
+ }
+
+ protected virtual IAsyncResult BeginExecute(RequestContext requestContext, AsyncCallback callback, object state)
+ {
+ if (DisableAsyncSupport)
+ {
+ // For backwards compat, we can disallow async support and just chain to the sync Execute() function.
+ Action action = () =>
+ {
+ Execute(requestContext);
+ };
+
+ return AsyncResultWrapper.BeginSynchronous(callback, state, action, _executeTag);
+ }
+ else
+ {
+ if (requestContext == null)
+ {
+ throw new ArgumentNullException("requestContext");
+ }
+
+ // Support Asynchronous behavior.
+ // Execute/ExecuteCore are no longer called.
+
+ VerifyExecuteCalledOnce();
+ Initialize(requestContext);
+ return AsyncResultWrapper.Begin(callback, state, BeginExecuteCore, EndExecuteCore, _executeTag);
+ }
+ }
+
+ protected virtual IAsyncResult BeginExecuteCore(AsyncCallback callback, object state)
+ {
+ // If code in this method needs to be updated, please also check the ExecuteCore() method
+ // of Controller to see if that code also must be updated.
+ PossiblyLoadTempData();
+ try
+ {
+ string actionName = RouteData.GetRequiredString("action");
+ IActionInvoker invoker = ActionInvoker;
+ IAsyncActionInvoker asyncInvoker = invoker as IAsyncActionInvoker;
+ if (asyncInvoker != null)
+ {
+ // asynchronous invocation
+ BeginInvokeDelegate beginDelegate = delegate(AsyncCallback asyncCallback, object asyncState)
+ {
+ return asyncInvoker.BeginInvokeAction(ControllerContext, actionName, asyncCallback, asyncState);
+ };
+
+ EndInvokeDelegate endDelegate = delegate(IAsyncResult asyncResult)
+ {
+ if (!asyncInvoker.EndInvokeAction(asyncResult))
+ {
+ HandleUnknownAction(actionName);
+ }
+ };
+
+ return AsyncResultWrapper.Begin(callback, state, beginDelegate, endDelegate, _executeCoreTag);
+ }
+ else
+ {
+ // synchronous invocation
+ Action action = () =>
+ {
+ if (!invoker.InvokeAction(ControllerContext, actionName))
+ {
+ HandleUnknownAction(actionName);
+ }
+ };
+ return AsyncResultWrapper.BeginSynchronous(callback, state, action, _executeCoreTag);
+ }
+ }
+ catch
+ {
+ PossiblySaveTempData();
+ throw;
+ }
+ }
+
+ protected virtual void EndExecute(IAsyncResult asyncResult)
+ {
+ AsyncResultWrapper.End(asyncResult, _executeTag);
+ }
+
+ protected virtual void EndExecuteCore(IAsyncResult asyncResult)
+ {
+ // If code in this method needs to be updated, please also check the ExecuteCore() method
+ // of Controller to see if that code also must be updated.
+
+ try
+ {
+ AsyncResultWrapper.End(asyncResult, _executeCoreTag);
+ }
+ finally
+ {
+ PossiblySaveTempData();
+ }
+ }
+
+ #region IActionFilter Members
+
+ void IActionFilter.OnActionExecuting(ActionExecutingContext filterContext)
+ {
+ OnActionExecuting(filterContext);
+ }
+
+ void IActionFilter.OnActionExecuted(ActionExecutedContext filterContext)
+ {
+ OnActionExecuted(filterContext);
+ }
+
+ #endregion
+
+ #region IAuthorizationFilter Members
+
+ void IAuthorizationFilter.OnAuthorization(AuthorizationContext filterContext)
+ {
+ OnAuthorization(filterContext);
+ }
+
+ #endregion
+
+ #region IExceptionFilter Members
+
+ void IExceptionFilter.OnException(ExceptionContext filterContext)
+ {
+ OnException(filterContext);
+ }
+
+ #endregion
+
+ #region IResultFilter Members
+
+ void IResultFilter.OnResultExecuting(ResultExecutingContext filterContext)
+ {
+ OnResultExecuting(filterContext);
+ }
+
+ void IResultFilter.OnResultExecuted(ResultExecutedContext filterContext)
+ {
+ OnResultExecuted(filterContext);
+ }
+
+ #endregion
+ }
+}
diff --git a/src/System.Web.Mvc/ControllerActionInvoker.cs b/src/System.Web.Mvc/ControllerActionInvoker.cs
new file mode 100644
index 00000000..64310b72
--- /dev/null
+++ b/src/System.Web.Mvc/ControllerActionInvoker.cs
@@ -0,0 +1,372 @@
+using System.Collections.Generic;
+using System.Diagnostics.CodeAnalysis;
+using System.Globalization;
+using System.Linq;
+using System.Threading;
+using System.Web.Mvc.Properties;
+using Microsoft.Web.Infrastructure.DynamicValidationHelper;
+
+namespace System.Web.Mvc
+{
+ public class ControllerActionInvoker : IActionInvoker
+ {
+ private static readonly ControllerDescriptorCache _staticDescriptorCache = new ControllerDescriptorCache();
+
+ private ModelBinderDictionary _binders;
+ private Func<ControllerContext, ActionDescriptor, IEnumerable<Filter>> _getFiltersThunk = FilterProviders.Providers.GetFilters;
+ private ControllerDescriptorCache _instanceDescriptorCache;
+
+ public ControllerActionInvoker()
+ {
+ }
+
+ internal ControllerActionInvoker(params object[] filters)
+ : this()
+ {
+ if (filters != null)
+ {
+ _getFiltersThunk = (cc, ad) => filters.Select(f => new Filter(f, FilterScope.Action, null));
+ }
+ }
+
+ [SuppressMessage("Microsoft.Usage", "CA2227:CollectionPropertiesShouldBeReadOnly", Justification = "Property is settable so that the dictionary can be provided for unit testing purposes.")]
+ protected internal ModelBinderDictionary Binders
+ {
+ get
+ {
+ if (_binders == null)
+ {
+ _binders = ModelBinders.Binders;
+ }
+ return _binders;
+ }
+ set { _binders = value; }
+ }
+
+ internal ControllerDescriptorCache DescriptorCache
+ {
+ get
+ {
+ if (_instanceDescriptorCache == null)
+ {
+ _instanceDescriptorCache = _staticDescriptorCache;
+ }
+ return _instanceDescriptorCache;
+ }
+ set { _instanceDescriptorCache = value; }
+ }
+
+ protected virtual ActionResult CreateActionResult(ControllerContext controllerContext, ActionDescriptor actionDescriptor, object actionReturnValue)
+ {
+ if (actionReturnValue == null)
+ {
+ return new EmptyResult();
+ }
+
+ ActionResult actionResult = (actionReturnValue as ActionResult) ??
+ new ContentResult { Content = Convert.ToString(actionReturnValue, CultureInfo.InvariantCulture) };
+ return actionResult;
+ }
+
+ protected virtual ControllerDescriptor GetControllerDescriptor(ControllerContext controllerContext)
+ {
+ Type controllerType = controllerContext.Controller.GetType();
+ ControllerDescriptor controllerDescriptor = DescriptorCache.GetDescriptor(controllerType, () => new ReflectedControllerDescriptor(controllerType));
+ return controllerDescriptor;
+ }
+
+ protected virtual ActionDescriptor FindAction(ControllerContext controllerContext, ControllerDescriptor controllerDescriptor, string actionName)
+ {
+ ActionDescriptor actionDescriptor = controllerDescriptor.FindAction(controllerContext, actionName);
+ return actionDescriptor;
+ }
+
+ protected virtual FilterInfo GetFilters(ControllerContext controllerContext, ActionDescriptor actionDescriptor)
+ {
+ return new FilterInfo(_getFiltersThunk(controllerContext, actionDescriptor));
+ }
+
+ private IModelBinder GetModelBinder(ParameterDescriptor parameterDescriptor)
+ {
+ // look on the parameter itself, then look in the global table
+ return parameterDescriptor.BindingInfo.Binder ?? Binders.GetBinder(parameterDescriptor.ParameterType);
+ }
+
+ protected virtual object GetParameterValue(ControllerContext controllerContext, ParameterDescriptor parameterDescriptor)
+ {
+ // collect all of the necessary binding properties
+ Type parameterType = parameterDescriptor.ParameterType;
+ IModelBinder binder = GetModelBinder(parameterDescriptor);
+ IValueProvider valueProvider = controllerContext.Controller.ValueProvider;
+ string parameterName = parameterDescriptor.BindingInfo.Prefix ?? parameterDescriptor.ParameterName;
+ Predicate<string> propertyFilter = GetPropertyFilter(parameterDescriptor);
+
+ // finally, call into the binder
+ ModelBindingContext bindingContext = new ModelBindingContext()
+ {
+ FallbackToEmptyPrefix = (parameterDescriptor.BindingInfo.Prefix == null), // only fall back if prefix not specified
+ ModelMetadata = ModelMetadataProviders.Current.GetMetadataForType(null, parameterType),
+ ModelName = parameterName,
+ ModelState = controllerContext.Controller.ViewData.ModelState,
+ PropertyFilter = propertyFilter,
+ ValueProvider = valueProvider
+ };
+
+ object result = binder.BindModel(controllerContext, bindingContext);
+ return result ?? parameterDescriptor.DefaultValue;
+ }
+
+ protected virtual IDictionary<string, object> GetParameterValues(ControllerContext controllerContext, ActionDescriptor actionDescriptor)
+ {
+ Dictionary<string, object> parametersDict = new Dictionary<string, object>(StringComparer.OrdinalIgnoreCase);
+ ParameterDescriptor[] parameterDescriptors = actionDescriptor.GetParameters();
+
+ foreach (ParameterDescriptor parameterDescriptor in parameterDescriptors)
+ {
+ parametersDict[parameterDescriptor.ParameterName] = GetParameterValue(controllerContext, parameterDescriptor);
+ }
+ return parametersDict;
+ }
+
+ private static Predicate<string> GetPropertyFilter(ParameterDescriptor parameterDescriptor)
+ {
+ ParameterBindingInfo bindingInfo = parameterDescriptor.BindingInfo;
+ return propertyName => BindAttribute.IsPropertyAllowed(propertyName, bindingInfo.Include.ToArray(), bindingInfo.Exclude.ToArray());
+ }
+
+ public virtual bool InvokeAction(ControllerContext controllerContext, string actionName)
+ {
+ if (controllerContext == null)
+ {
+ throw new ArgumentNullException("controllerContext");
+ }
+ if (String.IsNullOrEmpty(actionName))
+ {
+ throw new ArgumentException(MvcResources.Common_NullOrEmpty, "actionName");
+ }
+
+ ControllerDescriptor controllerDescriptor = GetControllerDescriptor(controllerContext);
+ ActionDescriptor actionDescriptor = FindAction(controllerContext, controllerDescriptor, actionName);
+ if (actionDescriptor != null)
+ {
+ FilterInfo filterInfo = GetFilters(controllerContext, actionDescriptor);
+
+ try
+ {
+ AuthorizationContext authContext = InvokeAuthorizationFilters(controllerContext, filterInfo.AuthorizationFilters, actionDescriptor);
+ if (authContext.Result != null)
+ {
+ // the auth filter signaled that we should let it short-circuit the request
+ InvokeActionResult(controllerContext, authContext.Result);
+ }
+ else
+ {
+ if (controllerContext.Controller.ValidateRequest)
+ {
+ ValidateRequest(controllerContext);
+ }
+
+ IDictionary<string, object> parameters = GetParameterValues(controllerContext, actionDescriptor);
+ ActionExecutedContext postActionContext = InvokeActionMethodWithFilters(controllerContext, filterInfo.ActionFilters, actionDescriptor, parameters);
+ InvokeActionResultWithFilters(controllerContext, filterInfo.ResultFilters, postActionContext.Result);
+ }
+ }
+ catch (ThreadAbortException)
+ {
+ // This type of exception occurs as a result of Response.Redirect(), but we special-case so that
+ // the filters don't see this as an error.
+ throw;
+ }
+ catch (Exception ex)
+ {
+ // something blew up, so execute the exception filters
+ ExceptionContext exceptionContext = InvokeExceptionFilters(controllerContext, filterInfo.ExceptionFilters, ex);
+ if (!exceptionContext.ExceptionHandled)
+ {
+ throw;
+ }
+ InvokeActionResult(controllerContext, exceptionContext.Result);
+ }
+
+ return true;
+ }
+
+ // notify controller that no method matched
+ return false;
+ }
+
+ protected virtual ActionResult InvokeActionMethod(ControllerContext controllerContext, ActionDescriptor actionDescriptor, IDictionary<string, object> parameters)
+ {
+ object returnValue = actionDescriptor.Execute(controllerContext, parameters);
+ ActionResult result = CreateActionResult(controllerContext, actionDescriptor, returnValue);
+ return result;
+ }
+
+ internal static ActionExecutedContext InvokeActionMethodFilter(IActionFilter filter, ActionExecutingContext preContext, Func<ActionExecutedContext> continuation)
+ {
+ filter.OnActionExecuting(preContext);
+ if (preContext.Result != null)
+ {
+ return new ActionExecutedContext(preContext, preContext.ActionDescriptor, true /* canceled */, null /* exception */)
+ {
+ Result = preContext.Result
+ };
+ }
+
+ bool wasError = false;
+ ActionExecutedContext postContext = null;
+ try
+ {
+ postContext = continuation();
+ }
+ catch (ThreadAbortException)
+ {
+ // This type of exception occurs as a result of Response.Redirect(), but we special-case so that
+ // the filters don't see this as an error.
+ postContext = new ActionExecutedContext(preContext, preContext.ActionDescriptor, false /* canceled */, null /* exception */);
+ filter.OnActionExecuted(postContext);
+ throw;
+ }
+ catch (Exception ex)
+ {
+ wasError = true;
+ postContext = new ActionExecutedContext(preContext, preContext.ActionDescriptor, false /* canceled */, ex);
+ filter.OnActionExecuted(postContext);
+ if (!postContext.ExceptionHandled)
+ {
+ throw;
+ }
+ }
+ if (!wasError)
+ {
+ filter.OnActionExecuted(postContext);
+ }
+ return postContext;
+ }
+
+ protected virtual ActionExecutedContext InvokeActionMethodWithFilters(ControllerContext controllerContext, IList<IActionFilter> filters, ActionDescriptor actionDescriptor, IDictionary<string, object> parameters)
+ {
+ ActionExecutingContext preContext = new ActionExecutingContext(controllerContext, actionDescriptor, parameters);
+ Func<ActionExecutedContext> continuation = () =>
+ new ActionExecutedContext(controllerContext, actionDescriptor, false /* canceled */, null /* exception */)
+ {
+ Result = InvokeActionMethod(controllerContext, actionDescriptor, parameters)
+ };
+
+ // need to reverse the filter list because the continuations are built up backward
+ Func<ActionExecutedContext> thunk = filters.Reverse().Aggregate(continuation,
+ (next, filter) => () => InvokeActionMethodFilter(filter, preContext, next));
+ return thunk();
+ }
+
+ protected virtual void InvokeActionResult(ControllerContext controllerContext, ActionResult actionResult)
+ {
+ actionResult.ExecuteResult(controllerContext);
+ }
+
+ internal static ResultExecutedContext InvokeActionResultFilter(IResultFilter filter, ResultExecutingContext preContext, Func<ResultExecutedContext> continuation)
+ {
+ filter.OnResultExecuting(preContext);
+ if (preContext.Cancel)
+ {
+ return new ResultExecutedContext(preContext, preContext.Result, true /* canceled */, null /* exception */);
+ }
+
+ bool wasError = false;
+ ResultExecutedContext postContext = null;
+ try
+ {
+ postContext = continuation();
+ }
+ catch (ThreadAbortException)
+ {
+ // This type of exception occurs as a result of Response.Redirect(), but we special-case so that
+ // the filters don't see this as an error.
+ postContext = new ResultExecutedContext(preContext, preContext.Result, false /* canceled */, null /* exception */);
+ filter.OnResultExecuted(postContext);
+ throw;
+ }
+ catch (Exception ex)
+ {
+ wasError = true;
+ postContext = new ResultExecutedContext(preContext, preContext.Result, false /* canceled */, ex);
+ filter.OnResultExecuted(postContext);
+ if (!postContext.ExceptionHandled)
+ {
+ throw;
+ }
+ }
+ if (!wasError)
+ {
+ filter.OnResultExecuted(postContext);
+ }
+ return postContext;
+ }
+
+ protected virtual ResultExecutedContext InvokeActionResultWithFilters(ControllerContext controllerContext, IList<IResultFilter> filters, ActionResult actionResult)
+ {
+ ResultExecutingContext preContext = new ResultExecutingContext(controllerContext, actionResult);
+ Func<ResultExecutedContext> continuation = delegate
+ {
+ InvokeActionResult(controllerContext, actionResult);
+ return new ResultExecutedContext(controllerContext, actionResult, false /* canceled */, null /* exception */);
+ };
+
+ // need to reverse the filter list because the continuations are built up backward
+ Func<ResultExecutedContext> thunk = filters.Reverse().Aggregate(continuation,
+ (next, filter) => () => InvokeActionResultFilter(filter, preContext, next));
+ return thunk();
+ }
+
+ protected virtual AuthorizationContext InvokeAuthorizationFilters(ControllerContext controllerContext, IList<IAuthorizationFilter> filters, ActionDescriptor actionDescriptor)
+ {
+ AuthorizationContext context = new AuthorizationContext(controllerContext, actionDescriptor);
+ foreach (IAuthorizationFilter filter in filters)
+ {
+ filter.OnAuthorization(context);
+ // short-circuit evaluation
+ if (context.Result != null)
+ {
+ break;
+ }
+ }
+
+ return context;
+ }
+
+ protected virtual ExceptionContext InvokeExceptionFilters(ControllerContext controllerContext, IList<IExceptionFilter> filters, Exception exception)
+ {
+ ExceptionContext context = new ExceptionContext(controllerContext, exception);
+ foreach (IExceptionFilter filter in filters.Reverse())
+ {
+ filter.OnException(context);
+ }
+
+ return context;
+ }
+
+ internal static void ValidateRequest(ControllerContext controllerContext)
+ {
+ if (controllerContext.IsChildAction)
+ {
+ return;
+ }
+
+ // DevDiv 214040: Enable Request Validation by default for all controller requests
+ //
+ // Earlier versions of this method dereferenced Request.RawUrl to force validation of
+ // that field. This was necessary for Routing before ASP.NET v4, which read the incoming
+ // path from RawUrl. Request validation has been moved earlier in the pipeline by default and
+ // routing no longer consumes this property, so we don't have to either.
+
+ // Tolerate null HttpContext for testing
+ HttpContext currentContext = HttpContext.Current;
+ if (currentContext != null)
+ {
+ ValidationUtility.EnableDynamicValidation(currentContext);
+ }
+
+ controllerContext.HttpContext.Request.ValidateInput();
+ }
+ }
+}
diff --git a/src/System.Web.Mvc/ControllerBase.cs b/src/System.Web.Mvc/ControllerBase.cs
new file mode 100644
index 00000000..f58696c6
--- /dev/null
+++ b/src/System.Web.Mvc/ControllerBase.cs
@@ -0,0 +1,130 @@
+using System.Diagnostics.CodeAnalysis;
+using System.Globalization;
+using System.Web.Mvc.Async;
+using System.Web.Mvc.Properties;
+using System.Web.Routing;
+using System.Web.WebPages.Scope;
+
+namespace System.Web.Mvc
+{
+ public abstract class ControllerBase : IController
+ {
+ private readonly SingleEntryGate _executeWasCalledGate = new SingleEntryGate();
+
+ private DynamicViewDataDictionary _dynamicViewDataDictionary;
+ private TempDataDictionary _tempDataDictionary;
+ private bool _validateRequest = true;
+ private IValueProvider _valueProvider;
+ private ViewDataDictionary _viewDataDictionary;
+
+ public ControllerContext ControllerContext { get; set; }
+
+ [SuppressMessage("Microsoft.Usage", "CA2227:CollectionPropertiesShouldBeReadOnly", Justification = "This property is settable so that unit tests can provide mock implementations.")]
+ public TempDataDictionary TempData
+ {
+ get
+ {
+ if (ControllerContext != null && ControllerContext.IsChildAction)
+ {
+ return ControllerContext.ParentActionViewContext.TempData;
+ }
+ if (_tempDataDictionary == null)
+ {
+ _tempDataDictionary = new TempDataDictionary();
+ }
+ return _tempDataDictionary;
+ }
+ set { _tempDataDictionary = value; }
+ }
+
+ public bool ValidateRequest
+ {
+ get { return _validateRequest; }
+ set { _validateRequest = value; }
+ }
+
+ public IValueProvider ValueProvider
+ {
+ get
+ {
+ if (_valueProvider == null)
+ {
+ _valueProvider = ValueProviderFactories.Factories.GetValueProvider(ControllerContext);
+ }
+ return _valueProvider;
+ }
+ set { _valueProvider = value; }
+ }
+
+ public dynamic ViewBag
+ {
+ get
+ {
+ if (_dynamicViewDataDictionary == null)
+ {
+ _dynamicViewDataDictionary = new DynamicViewDataDictionary(() => ViewData);
+ }
+ return _dynamicViewDataDictionary;
+ }
+ }
+
+ [SuppressMessage("Microsoft.Usage", "CA2227:CollectionPropertiesShouldBeReadOnly", Justification = "This property is settable so that unit tests can provide mock implementations.")]
+ public ViewDataDictionary ViewData
+ {
+ get
+ {
+ if (_viewDataDictionary == null)
+ {
+ _viewDataDictionary = new ViewDataDictionary();
+ }
+ return _viewDataDictionary;
+ }
+ set { _viewDataDictionary = value; }
+ }
+
+ protected virtual void Execute(RequestContext requestContext)
+ {
+ if (requestContext == null)
+ {
+ throw new ArgumentNullException("requestContext");
+ }
+ if (requestContext.HttpContext == null)
+ {
+ throw new ArgumentException(MvcResources.ControllerBase_CannotExecuteWithNullHttpContext, "requestContext");
+ }
+
+ VerifyExecuteCalledOnce();
+ Initialize(requestContext);
+
+ using (ScopeStorage.CreateTransientScope())
+ {
+ ExecuteCore();
+ }
+ }
+
+ protected abstract void ExecuteCore();
+
+ protected virtual void Initialize(RequestContext requestContext)
+ {
+ ControllerContext = new ControllerContext(requestContext, this);
+ }
+
+ internal void VerifyExecuteCalledOnce()
+ {
+ if (!_executeWasCalledGate.TryEnter())
+ {
+ string message = String.Format(CultureInfo.CurrentCulture, MvcResources.ControllerBase_CannotHandleMultipleRequests, GetType());
+ throw new InvalidOperationException(message);
+ }
+ }
+
+ #region IController Members
+
+ void IController.Execute(RequestContext requestContext)
+ {
+ Execute(requestContext);
+ }
+
+ #endregion
+ }
+}
diff --git a/src/System.Web.Mvc/ControllerBuilder.cs b/src/System.Web.Mvc/ControllerBuilder.cs
new file mode 100644
index 00000000..004f5361
--- /dev/null
+++ b/src/System.Web.Mvc/ControllerBuilder.cs
@@ -0,0 +1,88 @@
+using System.Collections.Generic;
+using System.Diagnostics.CodeAnalysis;
+using System.Globalization;
+using System.Web.Mvc.Properties;
+
+namespace System.Web.Mvc
+{
+ public class ControllerBuilder
+ {
+ private static ControllerBuilder _instance = new ControllerBuilder();
+ private Func<IControllerFactory> _factoryThunk = () => null;
+ private HashSet<string> _namespaces = new HashSet<string>(StringComparer.OrdinalIgnoreCase);
+ private IResolver<IControllerFactory> _serviceResolver;
+
+ public ControllerBuilder()
+ : this(null)
+ {
+ }
+
+ internal ControllerBuilder(IResolver<IControllerFactory> serviceResolver)
+ {
+ _serviceResolver = serviceResolver ?? new SingleServiceResolver<IControllerFactory>(
+ () => _factoryThunk(),
+ new DefaultControllerFactory { ControllerBuilder = this },
+ "ControllerBuilder.GetControllerFactory");
+ }
+
+ public static ControllerBuilder Current
+ {
+ get { return _instance; }
+ }
+
+ public HashSet<string> DefaultNamespaces
+ {
+ get { return _namespaces; }
+ }
+
+ [SuppressMessage("Microsoft.Design", "CA1024:UsePropertiesWhereAppropriate", Justification = "Calling method multiple times might return different objects.")]
+ public IControllerFactory GetControllerFactory()
+ {
+ return _serviceResolver.Current;
+ }
+
+ public void SetControllerFactory(IControllerFactory controllerFactory)
+ {
+ if (controllerFactory == null)
+ {
+ throw new ArgumentNullException("controllerFactory");
+ }
+
+ _factoryThunk = () => controllerFactory;
+ }
+
+ public void SetControllerFactory(Type controllerFactoryType)
+ {
+ if (controllerFactoryType == null)
+ {
+ throw new ArgumentNullException("controllerFactoryType");
+ }
+ if (!typeof(IControllerFactory).IsAssignableFrom(controllerFactoryType))
+ {
+ throw new ArgumentException(
+ String.Format(
+ CultureInfo.CurrentCulture,
+ MvcResources.ControllerBuilder_MissingIControllerFactory,
+ controllerFactoryType),
+ "controllerFactoryType");
+ }
+
+ _factoryThunk = delegate
+ {
+ try
+ {
+ return (IControllerFactory)Activator.CreateInstance(controllerFactoryType);
+ }
+ catch (Exception ex)
+ {
+ throw new InvalidOperationException(
+ String.Format(
+ CultureInfo.CurrentCulture,
+ MvcResources.ControllerBuilder_ErrorCreatingControllerFactory,
+ controllerFactoryType),
+ ex);
+ }
+ };
+ }
+ }
+}
diff --git a/src/System.Web.Mvc/ControllerContext.cs b/src/System.Web.Mvc/ControllerContext.cs
new file mode 100644
index 00000000..54bfcd46
--- /dev/null
+++ b/src/System.Web.Mvc/ControllerContext.cs
@@ -0,0 +1,133 @@
+using System.Diagnostics.CodeAnalysis;
+using System.Web.Routing;
+using System.Web.WebPages;
+
+namespace System.Web.Mvc
+{
+ // Though many of the properties on ControllerContext and its subclassed types are virtual, there are still sealed
+ // properties (like ControllerContext.RequestContext, ActionExecutingContext.Result, etc.). If these properties
+ // were virtual, a mocking framework might override them with incorrect behavior (property getters would return
+ // null, property setters would be no-ops). By sealing these properties, we are forcing them to have the default
+ // "get or store a value" semantics that they were intended to have.
+
+ public class ControllerContext
+ {
+ internal const string ParentActionViewContextToken = "ParentActionViewContext";
+ private HttpContextBase _httpContext;
+ private RequestContext _requestContext;
+ private RouteData _routeData;
+
+ // parameterless constructor used for mocking
+ public ControllerContext()
+ {
+ }
+
+ // copy constructor - allows for subclassed types to take an existing ControllerContext as a parameter
+ // and we'll automatically set the appropriate properties
+ [SuppressMessage("Microsoft.Usage", "CA2214:DoNotCallOverridableMethodsInConstructors", Justification = "The virtual property setters are only to support mocking frameworks, in which case this constructor shouldn't be called anyway.")]
+ protected ControllerContext(ControllerContext controllerContext)
+ {
+ if (controllerContext == null)
+ {
+ throw new ArgumentNullException("controllerContext");
+ }
+
+ Controller = controllerContext.Controller;
+ RequestContext = controllerContext.RequestContext;
+ }
+
+ public ControllerContext(HttpContextBase httpContext, RouteData routeData, ControllerBase controller)
+ : this(new RequestContext(httpContext, routeData), controller)
+ {
+ }
+
+ [SuppressMessage("Microsoft.Usage", "CA2214:DoNotCallOverridableMethodsInConstructors", Justification = "The virtual property setters are only to support mocking frameworks, in which case this constructor shouldn't be called anyway.")]
+ public ControllerContext(RequestContext requestContext, ControllerBase controller)
+ {
+ if (requestContext == null)
+ {
+ throw new ArgumentNullException("requestContext");
+ }
+ if (controller == null)
+ {
+ throw new ArgumentNullException("controller");
+ }
+
+ RequestContext = requestContext;
+ Controller = controller;
+ }
+
+ public virtual ControllerBase Controller { get; set; }
+
+ public IDisplayMode DisplayMode
+ {
+ get { return DisplayModeProvider.GetDisplayMode(HttpContext); }
+ set { DisplayModeProvider.SetDisplayMode(HttpContext, value); }
+ }
+
+ public virtual HttpContextBase HttpContext
+ {
+ get
+ {
+ if (_httpContext == null)
+ {
+ _httpContext = (_requestContext != null) ? _requestContext.HttpContext : new EmptyHttpContext();
+ }
+ return _httpContext;
+ }
+ set { _httpContext = value; }
+ }
+
+ public virtual bool IsChildAction
+ {
+ get
+ {
+ RouteData routeData = RouteData;
+ if (routeData == null)
+ {
+ return false;
+ }
+ return routeData.DataTokens.ContainsKey(ParentActionViewContextToken);
+ }
+ }
+
+ public ViewContext ParentActionViewContext
+ {
+ get { return RouteData.DataTokens[ParentActionViewContextToken] as ViewContext; }
+ }
+
+ public RequestContext RequestContext
+ {
+ get
+ {
+ if (_requestContext == null)
+ {
+ // still need explicit calls to constructors since the property getters are virtual and might return null
+ HttpContextBase httpContext = HttpContext ?? new EmptyHttpContext();
+ RouteData routeData = RouteData ?? new RouteData();
+
+ _requestContext = new RequestContext(httpContext, routeData);
+ }
+ return _requestContext;
+ }
+ set { _requestContext = value; }
+ }
+
+ public virtual RouteData RouteData
+ {
+ get
+ {
+ if (_routeData == null)
+ {
+ _routeData = (_requestContext != null) ? _requestContext.RouteData : new RouteData();
+ }
+ return _routeData;
+ }
+ set { _routeData = value; }
+ }
+
+ private sealed class EmptyHttpContext : HttpContextBase
+ {
+ }
+ }
+}
diff --git a/src/System.Web.Mvc/ControllerDescriptor.cs b/src/System.Web.Mvc/ControllerDescriptor.cs
new file mode 100644
index 00000000..45c0af9b
--- /dev/null
+++ b/src/System.Web.Mvc/ControllerDescriptor.cs
@@ -0,0 +1,78 @@
+using System.Collections.Generic;
+using System.Diagnostics.CodeAnalysis;
+using System.Linq;
+using System.Reflection;
+
+namespace System.Web.Mvc
+{
+ public abstract class ControllerDescriptor : ICustomAttributeProvider, IUniquelyIdentifiable
+ {
+ private readonly Lazy<string> _uniqueId;
+
+ protected ControllerDescriptor()
+ {
+ _uniqueId = new Lazy<string>(CreateUniqueId);
+ }
+
+ public virtual string ControllerName
+ {
+ get
+ {
+ string typeName = ControllerType.Name;
+ if (typeName.EndsWith("Controller", StringComparison.OrdinalIgnoreCase))
+ {
+ return typeName.Substring(0, typeName.Length - "Controller".Length);
+ }
+
+ return typeName;
+ }
+ }
+
+ public abstract Type ControllerType { get; }
+
+ [SuppressMessage("Microsoft.Security", "CA2119:SealMethodsThatSatisfyPrivateInterfaces", Justification = "This is overridden elsewhere in System.Web.Mvc")]
+ public virtual string UniqueId
+ {
+ get { return _uniqueId.Value; }
+ }
+
+ private string CreateUniqueId()
+ {
+ return DescriptorUtil.CreateUniqueId(GetType(), ControllerName, ControllerType);
+ }
+
+ public abstract ActionDescriptor FindAction(ControllerContext controllerContext, string actionName);
+
+ public abstract ActionDescriptor[] GetCanonicalActions();
+
+ public virtual object[] GetCustomAttributes(bool inherit)
+ {
+ return GetCustomAttributes(typeof(object), inherit);
+ }
+
+ public virtual object[] GetCustomAttributes(Type attributeType, bool inherit)
+ {
+ if (attributeType == null)
+ {
+ throw new ArgumentNullException("attributeType");
+ }
+
+ return (object[])Array.CreateInstance(attributeType, 0);
+ }
+
+ public virtual IEnumerable<FilterAttribute> GetFilterAttributes(bool useCache)
+ {
+ return GetCustomAttributes(typeof(FilterAttribute), inherit: true).Cast<FilterAttribute>();
+ }
+
+ public virtual bool IsDefined(Type attributeType, bool inherit)
+ {
+ if (attributeType == null)
+ {
+ throw new ArgumentNullException("attributeType");
+ }
+
+ return false;
+ }
+ }
+}
diff --git a/src/System.Web.Mvc/ControllerDescriptorCache.cs b/src/System.Web.Mvc/ControllerDescriptorCache.cs
new file mode 100644
index 00000000..538e03a0
--- /dev/null
+++ b/src/System.Web.Mvc/ControllerDescriptorCache.cs
@@ -0,0 +1,14 @@
+namespace System.Web.Mvc
+{
+ internal sealed class ControllerDescriptorCache : ReaderWriterCache<Type, ControllerDescriptor>
+ {
+ public ControllerDescriptorCache()
+ {
+ }
+
+ public ControllerDescriptor GetDescriptor(Type controllerType, Func<ControllerDescriptor> creator)
+ {
+ return FetchOrCreateItem(controllerType, creator);
+ }
+ }
+}
diff --git a/src/System.Web.Mvc/ControllerInstanceFilterProvider.cs b/src/System.Web.Mvc/ControllerInstanceFilterProvider.cs
new file mode 100644
index 00000000..89ee3350
--- /dev/null
+++ b/src/System.Web.Mvc/ControllerInstanceFilterProvider.cs
@@ -0,0 +1,16 @@
+using System.Collections.Generic;
+
+namespace System.Web.Mvc
+{
+ public class ControllerInstanceFilterProvider : IFilterProvider
+ {
+ public IEnumerable<Filter> GetFilters(ControllerContext controllerContext, ActionDescriptor actionDescriptor)
+ {
+ if (controllerContext.Controller != null)
+ {
+ // Use FilterScope.First and Order of Int32.MinValue to ensure controller instance methods always run first
+ yield return new Filter(controllerContext.Controller, FilterScope.First, Int32.MinValue);
+ }
+ }
+ }
+}
diff --git a/src/System.Web.Mvc/ControllerTypeCache.cs b/src/System.Web.Mvc/ControllerTypeCache.cs
new file mode 100644
index 00000000..7b560f4a
--- /dev/null
+++ b/src/System.Web.Mvc/ControllerTypeCache.cs
@@ -0,0 +1,138 @@
+using System.Collections.Generic;
+using System.Linq;
+
+namespace System.Web.Mvc
+{
+ internal sealed class ControllerTypeCache
+ {
+ private const string TypeCacheName = "MVC-ControllerTypeCache.xml";
+
+ private Dictionary<string, ILookup<string, Type>> _cache;
+ private object _lockObj = new object();
+
+ internal int Count
+ {
+ get
+ {
+ int count = 0;
+ foreach (var lookup in _cache.Values)
+ {
+ foreach (var grouping in lookup)
+ {
+ count += grouping.Count();
+ }
+ }
+ return count;
+ }
+ }
+
+ public void EnsureInitialized(IBuildManager buildManager)
+ {
+ if (_cache == null)
+ {
+ lock (_lockObj)
+ {
+ if (_cache == null)
+ {
+ List<Type> controllerTypes = TypeCacheUtil.GetFilteredTypesFromAssemblies(TypeCacheName, IsControllerType, buildManager);
+ var groupedByName = controllerTypes.GroupBy(
+ t => t.Name.Substring(0, t.Name.Length - "Controller".Length),
+ StringComparer.OrdinalIgnoreCase);
+ _cache = groupedByName.ToDictionary(
+ g => g.Key,
+ g => g.ToLookup(t => t.Namespace ?? String.Empty, StringComparer.OrdinalIgnoreCase),
+ StringComparer.OrdinalIgnoreCase);
+ }
+ }
+ }
+ }
+
+ public ICollection<Type> GetControllerTypes(string controllerName, HashSet<string> namespaces)
+ {
+ HashSet<Type> matchingTypes = new HashSet<Type>();
+
+ ILookup<string, Type> namespaceLookup;
+ if (_cache.TryGetValue(controllerName, out namespaceLookup))
+ {
+ // this friendly name was located in the cache, now cycle through namespaces
+ if (namespaces != null)
+ {
+ foreach (string requestedNamespace in namespaces)
+ {
+ foreach (var targetNamespaceGrouping in namespaceLookup)
+ {
+ if (IsNamespaceMatch(requestedNamespace, targetNamespaceGrouping.Key))
+ {
+ matchingTypes.UnionWith(targetNamespaceGrouping);
+ }
+ }
+ }
+ }
+ else
+ {
+ // if the namespaces parameter is null, search *every* namespace
+ foreach (var namespaceGroup in namespaceLookup)
+ {
+ matchingTypes.UnionWith(namespaceGroup);
+ }
+ }
+ }
+
+ return matchingTypes;
+ }
+
+ internal static bool IsControllerType(Type t)
+ {
+ return
+ t != null &&
+ t.IsPublic &&
+ t.Name.EndsWith("Controller", StringComparison.OrdinalIgnoreCase) &&
+ !t.IsAbstract &&
+ typeof(IController).IsAssignableFrom(t);
+ }
+
+ internal static bool IsNamespaceMatch(string requestedNamespace, string targetNamespace)
+ {
+ // degenerate cases
+ if (requestedNamespace == null)
+ {
+ return false;
+ }
+ else if (requestedNamespace.Length == 0)
+ {
+ return true;
+ }
+
+ if (!requestedNamespace.EndsWith(".*", StringComparison.OrdinalIgnoreCase))
+ {
+ // looking for exact namespace match
+ return String.Equals(requestedNamespace, targetNamespace, StringComparison.OrdinalIgnoreCase);
+ }
+ else
+ {
+ // looking for exact or sub-namespace match
+ requestedNamespace = requestedNamespace.Substring(0, requestedNamespace.Length - ".*".Length);
+ if (!targetNamespace.StartsWith(requestedNamespace, StringComparison.OrdinalIgnoreCase))
+ {
+ return false;
+ }
+
+ if (requestedNamespace.Length == targetNamespace.Length)
+ {
+ // exact match
+ return true;
+ }
+ else if (targetNamespace[requestedNamespace.Length] == '.')
+ {
+ // good prefix match, e.g. requestedNamespace = "Foo.Bar" and targetNamespace = "Foo.Bar.Baz"
+ return true;
+ }
+ else
+ {
+ // bad prefix match, e.g. requestedNamespace = "Foo.Bar" and targetNamespace = "Foo.Bar2"
+ return false;
+ }
+ }
+ }
+ }
+}
diff --git a/src/System.Web.Mvc/CustomModelBinderAttribute.cs b/src/System.Web.Mvc/CustomModelBinderAttribute.cs
new file mode 100644
index 00000000..c2e676f9
--- /dev/null
+++ b/src/System.Web.Mvc/CustomModelBinderAttribute.cs
@@ -0,0 +1,13 @@
+using System.Diagnostics.CodeAnalysis;
+
+namespace System.Web.Mvc
+{
+ [AttributeUsage(ValidTargets, AllowMultiple = false, Inherited = false)]
+ public abstract class CustomModelBinderAttribute : Attribute
+ {
+ internal const AttributeTargets ValidTargets = AttributeTargets.Class | AttributeTargets.Enum | AttributeTargets.Interface | AttributeTargets.Parameter | AttributeTargets.Struct;
+
+ [SuppressMessage("Microsoft.Design", "CA1024:UsePropertiesWhereAppropriate", Justification = "This method can potentially perform a non-trivial amount of work.")]
+ public abstract IModelBinder GetBinder();
+ }
+}
diff --git a/src/System.Web.Mvc/DataAnnotationsModelMetadata.cs b/src/System.Web.Mvc/DataAnnotationsModelMetadata.cs
new file mode 100644
index 00000000..9b8156e3
--- /dev/null
+++ b/src/System.Web.Mvc/DataAnnotationsModelMetadata.cs
@@ -0,0 +1,60 @@
+using System.ComponentModel.DataAnnotations;
+using System.Globalization;
+using System.Reflection;
+using System.Web.Mvc.Properties;
+
+namespace System.Web.Mvc
+{
+ public class DataAnnotationsModelMetadata : ModelMetadata
+ {
+ private DisplayColumnAttribute _displayColumnAttribute;
+
+ public DataAnnotationsModelMetadata(DataAnnotationsModelMetadataProvider provider, Type containerType,
+ Func<object> modelAccessor, Type modelType, string propertyName,
+ DisplayColumnAttribute displayColumnAttribute)
+ : base(provider, containerType, modelAccessor, modelType, propertyName)
+ {
+ _displayColumnAttribute = displayColumnAttribute;
+ }
+
+ protected override string GetSimpleDisplayText()
+ {
+ if (Model != null)
+ {
+ if (_displayColumnAttribute != null && !String.IsNullOrEmpty(_displayColumnAttribute.DisplayColumn))
+ {
+ PropertyInfo displayColumnProperty = ModelType.GetProperty(_displayColumnAttribute.DisplayColumn, BindingFlags.Public | BindingFlags.IgnoreCase | BindingFlags.Instance);
+ ValidateDisplayColumnAttribute(_displayColumnAttribute, displayColumnProperty, ModelType);
+
+ object simpleDisplayTextValue = displayColumnProperty.GetValue(Model, new object[0]);
+ if (simpleDisplayTextValue != null)
+ {
+ return simpleDisplayTextValue.ToString();
+ }
+ }
+ }
+
+ return base.GetSimpleDisplayText();
+ }
+
+ private static void ValidateDisplayColumnAttribute(DisplayColumnAttribute displayColumnAttribute, PropertyInfo displayColumnProperty, Type modelType)
+ {
+ if (displayColumnProperty == null)
+ {
+ throw new InvalidOperationException(
+ String.Format(
+ CultureInfo.CurrentCulture,
+ MvcResources.DataAnnotationsModelMetadataProvider_UnknownProperty,
+ modelType.FullName, displayColumnAttribute.DisplayColumn));
+ }
+ if (displayColumnProperty.GetGetMethod() == null)
+ {
+ throw new InvalidOperationException(
+ String.Format(
+ CultureInfo.CurrentCulture,
+ MvcResources.DataAnnotationsModelMetadataProvider_UnreadableProperty,
+ modelType.FullName, displayColumnAttribute.DisplayColumn));
+ }
+ }
+ }
+}
diff --git a/src/System.Web.Mvc/DataAnnotationsModelMetadataProvider.cs b/src/System.Web.Mvc/DataAnnotationsModelMetadataProvider.cs
new file mode 100644
index 00000000..7ffef9c7
--- /dev/null
+++ b/src/System.Web.Mvc/DataAnnotationsModelMetadataProvider.cs
@@ -0,0 +1,115 @@
+using System.Collections.Generic;
+using System.ComponentModel;
+using System.ComponentModel.DataAnnotations;
+using System.Linq;
+
+namespace System.Web.Mvc
+{
+ public class DataAnnotationsModelMetadataProvider : AssociatedMetadataProvider
+ {
+ protected override ModelMetadata CreateMetadata(IEnumerable<Attribute> attributes, Type containerType, Func<object> modelAccessor, Type modelType, string propertyName)
+ {
+ List<Attribute> attributeList = new List<Attribute>(attributes);
+ DisplayColumnAttribute displayColumnAttribute = attributeList.OfType<DisplayColumnAttribute>().FirstOrDefault();
+ DataAnnotationsModelMetadata result = new DataAnnotationsModelMetadata(this, containerType, modelAccessor, modelType, propertyName, displayColumnAttribute);
+
+ // Do [HiddenInput] before [UIHint], so you can override the template hint
+ HiddenInputAttribute hiddenInputAttribute = attributeList.OfType<HiddenInputAttribute>().FirstOrDefault();
+ if (hiddenInputAttribute != null)
+ {
+ result.TemplateHint = "HiddenInput";
+ result.HideSurroundingHtml = !hiddenInputAttribute.DisplayValue;
+ }
+
+ // We prefer [UIHint("...", PresentationLayer = "MVC")] but will fall back to [UIHint("...")]
+ IEnumerable<UIHintAttribute> uiHintAttributes = attributeList.OfType<UIHintAttribute>();
+ UIHintAttribute uiHintAttribute = uiHintAttributes.FirstOrDefault(a => String.Equals(a.PresentationLayer, "MVC", StringComparison.OrdinalIgnoreCase))
+ ?? uiHintAttributes.FirstOrDefault(a => String.IsNullOrEmpty(a.PresentationLayer));
+ if (uiHintAttribute != null)
+ {
+ result.TemplateHint = uiHintAttribute.UIHint;
+ }
+
+ DataTypeAttribute dataTypeAttribute = attributeList.OfType<DataTypeAttribute>().FirstOrDefault();
+ if (dataTypeAttribute != null)
+ {
+ result.DataTypeName = dataTypeAttribute.ToDataTypeName();
+ }
+
+ EditableAttribute editable = attributes.OfType<EditableAttribute>().FirstOrDefault();
+ if (editable != null)
+ {
+ result.IsReadOnly = !editable.AllowEdit;
+ }
+ else
+ {
+ ReadOnlyAttribute readOnlyAttribute = attributeList.OfType<ReadOnlyAttribute>().FirstOrDefault();
+ if (readOnlyAttribute != null)
+ {
+ result.IsReadOnly = readOnlyAttribute.IsReadOnly;
+ }
+ }
+
+ DisplayFormatAttribute displayFormatAttribute = attributeList.OfType<DisplayFormatAttribute>().FirstOrDefault();
+ if (displayFormatAttribute == null && dataTypeAttribute != null)
+ {
+ displayFormatAttribute = dataTypeAttribute.DisplayFormat;
+ }
+ if (displayFormatAttribute != null)
+ {
+ result.NullDisplayText = displayFormatAttribute.NullDisplayText;
+ result.DisplayFormatString = displayFormatAttribute.DataFormatString;
+ result.ConvertEmptyStringToNull = displayFormatAttribute.ConvertEmptyStringToNull;
+
+ if (displayFormatAttribute.ApplyFormatInEditMode)
+ {
+ result.EditFormatString = displayFormatAttribute.DataFormatString;
+ }
+
+ if (!displayFormatAttribute.HtmlEncode && String.IsNullOrWhiteSpace(result.DataTypeName))
+ {
+ result.DataTypeName = DataTypeUtil.HtmlTypeName;
+ }
+ }
+
+ ScaffoldColumnAttribute scaffoldColumnAttribute = attributeList.OfType<ScaffoldColumnAttribute>().FirstOrDefault();
+ if (scaffoldColumnAttribute != null)
+ {
+ result.ShowForDisplay = result.ShowForEdit = scaffoldColumnAttribute.Scaffold;
+ }
+
+ DisplayAttribute display = attributes.OfType<DisplayAttribute>().FirstOrDefault();
+ string name = null;
+ if (display != null)
+ {
+ result.Description = display.GetDescription();
+ result.ShortDisplayName = display.GetShortName();
+ result.Watermark = display.GetPrompt();
+ result.Order = display.GetOrder() ?? ModelMetadata.DefaultOrder;
+
+ name = display.GetName();
+ }
+
+ if (name != null)
+ {
+ result.DisplayName = name;
+ }
+ else
+ {
+ DisplayNameAttribute displayNameAttribute = attributeList.OfType<DisplayNameAttribute>().FirstOrDefault();
+ if (displayNameAttribute != null)
+ {
+ result.DisplayName = displayNameAttribute.DisplayName;
+ }
+ }
+
+ RequiredAttribute requiredAttribute = attributeList.OfType<RequiredAttribute>().FirstOrDefault();
+ if (requiredAttribute != null)
+ {
+ result.IsRequired = true;
+ }
+
+ return result;
+ }
+ }
+}
diff --git a/src/System.Web.Mvc/DataAnnotationsModelValidator.cs b/src/System.Web.Mvc/DataAnnotationsModelValidator.cs
new file mode 100644
index 00000000..1bb8952b
--- /dev/null
+++ b/src/System.Web.Mvc/DataAnnotationsModelValidator.cs
@@ -0,0 +1,67 @@
+using System.Collections.Generic;
+using System.ComponentModel.DataAnnotations;
+using System.Linq;
+
+namespace System.Web.Mvc
+{
+ public class DataAnnotationsModelValidator : ModelValidator
+ {
+ public DataAnnotationsModelValidator(ModelMetadata metadata, ControllerContext context, ValidationAttribute attribute)
+ : base(metadata, context)
+ {
+ if (attribute == null)
+ {
+ throw new ArgumentNullException("attribute");
+ }
+
+ Attribute = attribute;
+ }
+
+ protected internal ValidationAttribute Attribute { get; private set; }
+
+ protected internal string ErrorMessage
+ {
+ get { return Attribute.FormatErrorMessage(Metadata.GetDisplayName()); }
+ }
+
+ public override bool IsRequired
+ {
+ get { return Attribute is RequiredAttribute; }
+ }
+
+ internal static ModelValidator Create(ModelMetadata metadata, ControllerContext context, ValidationAttribute attribute)
+ {
+ return new DataAnnotationsModelValidator(metadata, context, attribute);
+ }
+
+ public override IEnumerable<ModelClientValidationRule> GetClientValidationRules()
+ {
+ IEnumerable<ModelClientValidationRule> results = base.GetClientValidationRules();
+
+ IClientValidatable clientValidatable = Attribute as IClientValidatable;
+ if (clientValidatable != null)
+ {
+ results = results.Concat(clientValidatable.GetClientValidationRules(Metadata, ControllerContext));
+ }
+
+ return results;
+ }
+
+ public override IEnumerable<ModelValidationResult> Validate(object container)
+ {
+ // Per the WCF RIA Services team, instance can never be null (if you have
+ // no parent, you pass yourself for the "instance" parameter).
+ ValidationContext context = new ValidationContext(container ?? Metadata.Model, null, null);
+ context.DisplayName = Metadata.GetDisplayName();
+
+ ValidationResult result = Attribute.GetValidationResult(Metadata.Model, context);
+ if (result != ValidationResult.Success)
+ {
+ yield return new ModelValidationResult
+ {
+ Message = result.ErrorMessage
+ };
+ }
+ }
+ }
+}
diff --git a/src/System.Web.Mvc/DataAnnotationsModelValidatorProvider.cs b/src/System.Web.Mvc/DataAnnotationsModelValidatorProvider.cs
new file mode 100644
index 00000000..26d80c96
--- /dev/null
+++ b/src/System.Web.Mvc/DataAnnotationsModelValidatorProvider.cs
@@ -0,0 +1,375 @@
+using System.Collections.Generic;
+using System.ComponentModel.DataAnnotations;
+using System.Globalization;
+using System.Linq;
+using System.Reflection;
+using System.Threading;
+using System.Web.Mvc.Properties;
+
+namespace System.Web.Mvc
+{
+ // A factory for validators based on ValidationAttribute
+ public delegate ModelValidator DataAnnotationsModelValidationFactory(ModelMetadata metadata, ControllerContext context, ValidationAttribute attribute);
+
+ // A factory for validators based on IValidatableObject
+ public delegate ModelValidator DataAnnotationsValidatableObjectAdapterFactory(ModelMetadata metadata, ControllerContext context);
+
+ /// <summary>
+ /// An implementation of <see cref="ModelValidatorProvider"/> which providers validators
+ /// for attributes which derive from <see cref="ValidationAttribute"/>. It also provides
+ /// a validator for types which implement <see cref="IValidatableObject"/>. To support
+ /// client side validation, you can either register adapters through the static methods
+ /// on this class, or by having your validation attributes implement
+ /// <see cref="IClientValidatable"/>. The logic to support IClientValidatable
+ /// is implemented in <see cref="DataAnnotationsModelValidator"/>.
+ /// </summary>
+ public class DataAnnotationsModelValidatorProvider : AssociatedValidatorProvider
+ {
+ private static bool _addImplicitRequiredAttributeForValueTypes = true;
+ private static ReaderWriterLockSlim _adaptersLock = new ReaderWriterLockSlim();
+
+ // Factories for validation attributes
+
+ internal static DataAnnotationsModelValidationFactory DefaultAttributeFactory =
+ (metadata, context, attribute) => new DataAnnotationsModelValidator(metadata, context, attribute);
+
+ internal static Dictionary<Type, DataAnnotationsModelValidationFactory> AttributeFactories = new Dictionary<Type, DataAnnotationsModelValidationFactory>()
+ {
+ {
+ typeof(RangeAttribute),
+ (metadata, context, attribute) => new RangeAttributeAdapter(metadata, context, (RangeAttribute)attribute)
+ },
+ {
+ typeof(RegularExpressionAttribute),
+ (metadata, context, attribute) => new RegularExpressionAttributeAdapter(metadata, context, (RegularExpressionAttribute)attribute)
+ },
+ {
+ typeof(RequiredAttribute),
+ (metadata, context, attribute) => new RequiredAttributeAdapter(metadata, context, (RequiredAttribute)attribute)
+ },
+ {
+ typeof(StringLengthAttribute),
+ (metadata, context, attribute) => new StringLengthAttributeAdapter(metadata, context, (StringLengthAttribute)attribute)
+ },
+ };
+
+ // Factories for IValidatableObject models
+
+ internal static DataAnnotationsValidatableObjectAdapterFactory DefaultValidatableFactory =
+ (metadata, context) => new ValidatableObjectAdapter(metadata, context);
+
+ internal static Dictionary<Type, DataAnnotationsValidatableObjectAdapterFactory> ValidatableFactories = new Dictionary<Type, DataAnnotationsValidatableObjectAdapterFactory>();
+
+ public static bool AddImplicitRequiredAttributeForValueTypes
+ {
+ get { return _addImplicitRequiredAttributeForValueTypes; }
+ set { _addImplicitRequiredAttributeForValueTypes = value; }
+ }
+
+ protected override IEnumerable<ModelValidator> GetValidators(ModelMetadata metadata, ControllerContext context, IEnumerable<Attribute> attributes)
+ {
+ _adaptersLock.EnterReadLock();
+
+ try
+ {
+ List<ModelValidator> results = new List<ModelValidator>();
+
+ // Add an implied [Required] attribute for any non-nullable value type,
+ // unless they've configured us not to do that.
+ if (AddImplicitRequiredAttributeForValueTypes &&
+ metadata.IsRequired &&
+ !attributes.Any(a => a is RequiredAttribute))
+ {
+ attributes = attributes.Concat(new[] { new RequiredAttribute() });
+ }
+
+ // Produce a validator for each validation attribute we find
+ foreach (ValidationAttribute attribute in attributes.OfType<ValidationAttribute>())
+ {
+ DataAnnotationsModelValidationFactory factory;
+ if (!AttributeFactories.TryGetValue(attribute.GetType(), out factory))
+ {
+ factory = DefaultAttributeFactory;
+ }
+ results.Add(factory(metadata, context, attribute));
+ }
+
+ // Produce a validator if the type supports IValidatableObject
+ if (typeof(IValidatableObject).IsAssignableFrom(metadata.ModelType))
+ {
+ DataAnnotationsValidatableObjectAdapterFactory factory;
+ if (!ValidatableFactories.TryGetValue(metadata.ModelType, out factory))
+ {
+ factory = DefaultValidatableFactory;
+ }
+ results.Add(factory(metadata, context));
+ }
+
+ return results;
+ }
+ finally
+ {
+ _adaptersLock.ExitReadLock();
+ }
+ }
+
+ #region Validation attribute adapter registration
+
+ public static void RegisterAdapter(Type attributeType, Type adapterType)
+ {
+ ValidateAttributeType(attributeType);
+ ValidateAttributeAdapterType(adapterType);
+ ConstructorInfo constructor = GetAttributeAdapterConstructor(attributeType, adapterType);
+
+ _adaptersLock.EnterWriteLock();
+
+ try
+ {
+ AttributeFactories[attributeType] = (metadata, context, attribute) => (ModelValidator)constructor.Invoke(new object[] { metadata, context, attribute });
+ }
+ finally
+ {
+ _adaptersLock.ExitWriteLock();
+ }
+ }
+
+ public static void RegisterAdapterFactory(Type attributeType, DataAnnotationsModelValidationFactory factory)
+ {
+ ValidateAttributeType(attributeType);
+ ValidateAttributeFactory(factory);
+
+ _adaptersLock.EnterWriteLock();
+
+ try
+ {
+ AttributeFactories[attributeType] = factory;
+ }
+ finally
+ {
+ _adaptersLock.ExitWriteLock();
+ }
+ }
+
+ public static void RegisterDefaultAdapter(Type adapterType)
+ {
+ ValidateAttributeAdapterType(adapterType);
+ ConstructorInfo constructor = GetAttributeAdapterConstructor(typeof(ValidationAttribute), adapterType);
+
+ DefaultAttributeFactory = (metadata, context, attribute) => (ModelValidator)constructor.Invoke(new object[] { metadata, context, attribute });
+ }
+
+ public static void RegisterDefaultAdapterFactory(DataAnnotationsModelValidationFactory factory)
+ {
+ ValidateAttributeFactory(factory);
+
+ DefaultAttributeFactory = factory;
+ }
+
+ // Helpers
+
+ private static ConstructorInfo GetAttributeAdapterConstructor(Type attributeType, Type adapterType)
+ {
+ ConstructorInfo constructor = adapterType.GetConstructor(new[] { typeof(ModelMetadata), typeof(ControllerContext), attributeType });
+ if (constructor == null)
+ {
+ throw new ArgumentException(
+ String.Format(
+ CultureInfo.CurrentCulture,
+ MvcResources.DataAnnotationsModelValidatorProvider_ConstructorRequirements,
+ adapterType.FullName,
+ typeof(ModelMetadata).FullName,
+ typeof(ControllerContext).FullName,
+ attributeType.FullName),
+ "adapterType");
+ }
+
+ return constructor;
+ }
+
+ private static void ValidateAttributeAdapterType(Type adapterType)
+ {
+ if (adapterType == null)
+ {
+ throw new ArgumentNullException("adapterType");
+ }
+ if (!typeof(ModelValidator).IsAssignableFrom(adapterType))
+ {
+ throw new ArgumentException(
+ String.Format(
+ CultureInfo.CurrentCulture,
+ MvcResources.Common_TypeMustDriveFromType,
+ adapterType.FullName,
+ typeof(ModelValidator).FullName),
+ "adapterType");
+ }
+ }
+
+ private static void ValidateAttributeType(Type attributeType)
+ {
+ if (attributeType == null)
+ {
+ throw new ArgumentNullException("attributeType");
+ }
+ if (!typeof(ValidationAttribute).IsAssignableFrom(attributeType))
+ {
+ throw new ArgumentException(
+ String.Format(
+ CultureInfo.CurrentCulture,
+ MvcResources.Common_TypeMustDriveFromType,
+ attributeType.FullName,
+ typeof(ValidationAttribute).FullName),
+ "attributeType");
+ }
+ }
+
+ private static void ValidateAttributeFactory(DataAnnotationsModelValidationFactory factory)
+ {
+ if (factory == null)
+ {
+ throw new ArgumentNullException("factory");
+ }
+ }
+
+ #endregion
+
+ #region IValidatableObject adapter registration
+
+ /// <summary>
+ /// Registers an adapter type for the given <paramref name="modelType"/>, which must
+ /// implement <see cref="IValidatableObject"/>. The adapter type must derive from
+ /// <see cref="ModelValidator"/> and it must contain a public constructor
+ /// which takes two parameters of types <see cref="ModelMetadata"/> and
+ /// <see cref="ControllerContext"/>.
+ /// </summary>
+ public static void RegisterValidatableObjectAdapter(Type modelType, Type adapterType)
+ {
+ ValidateValidatableModelType(modelType);
+ ValidateValidatableAdapterType(adapterType);
+ ConstructorInfo constructor = GetValidatableAdapterConstructor(adapterType);
+
+ _adaptersLock.EnterWriteLock();
+
+ try
+ {
+ ValidatableFactories[modelType] = (metadata, context) => (ModelValidator)constructor.Invoke(new object[] { metadata, context });
+ }
+ finally
+ {
+ _adaptersLock.ExitWriteLock();
+ }
+ }
+
+ /// <summary>
+ /// Registers an adapter factory for the given <paramref name="modelType"/>, which must
+ /// implement <see cref="IValidatableObject"/>.
+ /// </summary>
+ public static void RegisterValidatableObjectAdapterFactory(Type modelType, DataAnnotationsValidatableObjectAdapterFactory factory)
+ {
+ ValidateValidatableModelType(modelType);
+ ValidateValidatableFactory(factory);
+
+ _adaptersLock.EnterWriteLock();
+
+ try
+ {
+ ValidatableFactories[modelType] = factory;
+ }
+ finally
+ {
+ _adaptersLock.ExitWriteLock();
+ }
+ }
+
+ /// <summary>
+ /// Registers the default adapter type for objects which implement
+ /// <see cref="IValidatableObject"/>. The adapter type must derive from
+ /// <see cref="ModelValidator"/> and it must contain a public constructor
+ /// which takes two parameters of types <see cref="ModelMetadata"/> and
+ /// <see cref="ControllerContext"/>.
+ /// </summary>
+ public static void RegisterDefaultValidatableObjectAdapter(Type adapterType)
+ {
+ ValidateValidatableAdapterType(adapterType);
+ ConstructorInfo constructor = GetValidatableAdapterConstructor(adapterType);
+
+ DefaultValidatableFactory = (metadata, context) => (ModelValidator)constructor.Invoke(new object[] { metadata, context });
+ }
+
+ /// <summary>
+ /// Registers the default adapter factory for objects which implement
+ /// <see cref="IValidatableObject"/>.
+ /// </summary>
+ public static void RegisterDefaultValidatableObjectAdapterFactory(DataAnnotationsValidatableObjectAdapterFactory factory)
+ {
+ ValidateValidatableFactory(factory);
+
+ DefaultValidatableFactory = factory;
+ }
+
+ // Helpers
+
+ private static ConstructorInfo GetValidatableAdapterConstructor(Type adapterType)
+ {
+ ConstructorInfo constructor = adapterType.GetConstructor(new[] { typeof(ModelMetadata), typeof(ControllerContext) });
+ if (constructor == null)
+ {
+ throw new ArgumentException(
+ String.Format(
+ CultureInfo.CurrentCulture,
+ MvcResources.DataAnnotationsModelValidatorProvider_ValidatableConstructorRequirements,
+ adapterType.FullName,
+ typeof(ModelMetadata).FullName,
+ typeof(ControllerContext).FullName),
+ "adapterType");
+ }
+
+ return constructor;
+ }
+
+ private static void ValidateValidatableAdapterType(Type adapterType)
+ {
+ if (adapterType == null)
+ {
+ throw new ArgumentNullException("adapterType");
+ }
+ if (!typeof(ModelValidator).IsAssignableFrom(adapterType))
+ {
+ throw new ArgumentException(
+ String.Format(
+ CultureInfo.CurrentCulture,
+ MvcResources.Common_TypeMustDriveFromType,
+ adapterType.FullName,
+ typeof(ModelValidator).FullName),
+ "adapterType");
+ }
+ }
+
+ private static void ValidateValidatableModelType(Type modelType)
+ {
+ if (modelType == null)
+ {
+ throw new ArgumentNullException("modelType");
+ }
+ if (!typeof(IValidatableObject).IsAssignableFrom(modelType))
+ {
+ throw new ArgumentException(
+ String.Format(
+ CultureInfo.CurrentCulture,
+ MvcResources.Common_TypeMustDriveFromType,
+ modelType.FullName,
+ typeof(IValidatableObject).FullName),
+ "modelType");
+ }
+ }
+
+ private static void ValidateValidatableFactory(DataAnnotationsValidatableObjectAdapterFactory factory)
+ {
+ if (factory == null)
+ {
+ throw new ArgumentNullException("factory");
+ }
+ }
+
+ #endregion
+ }
+}
diff --git a/src/System.Web.Mvc/DataAnnotationsModelValidator`1.cs b/src/System.Web.Mvc/DataAnnotationsModelValidator`1.cs
new file mode 100644
index 00000000..19a55969
--- /dev/null
+++ b/src/System.Web.Mvc/DataAnnotationsModelValidator`1.cs
@@ -0,0 +1,18 @@
+using System.ComponentModel.DataAnnotations;
+
+namespace System.Web.Mvc
+{
+ public class DataAnnotationsModelValidator<TAttribute> : DataAnnotationsModelValidator
+ where TAttribute : ValidationAttribute
+ {
+ public DataAnnotationsModelValidator(ModelMetadata metadata, ControllerContext context, TAttribute attribute)
+ : base(metadata, context, attribute)
+ {
+ }
+
+ protected new TAttribute Attribute
+ {
+ get { return (TAttribute)base.Attribute; }
+ }
+ }
+}
diff --git a/src/System.Web.Mvc/DataErrorInfoModelValidatorProvider.cs b/src/System.Web.Mvc/DataErrorInfoModelValidatorProvider.cs
new file mode 100644
index 00000000..cdfc72dc
--- /dev/null
+++ b/src/System.Web.Mvc/DataErrorInfoModelValidatorProvider.cs
@@ -0,0 +1,95 @@
+using System.Collections.Generic;
+using System.ComponentModel;
+using System.Linq;
+
+namespace System.Web.Mvc
+{
+ public class DataErrorInfoModelValidatorProvider : ModelValidatorProvider
+ {
+ public override IEnumerable<ModelValidator> GetValidators(ModelMetadata metadata, ControllerContext context)
+ {
+ if (metadata == null)
+ {
+ throw new ArgumentNullException("metadata");
+ }
+ if (context == null)
+ {
+ throw new ArgumentNullException("context");
+ }
+
+ return GetValidatorsImpl(metadata, context);
+ }
+
+ private static IEnumerable<ModelValidator> GetValidatorsImpl(ModelMetadata metadata, ControllerContext context)
+ {
+ // If the metadata describes a model that implements IDataErrorInfo, we should call its
+ // Error property at the appropriate time.
+ if (TypeImplementsIDataErrorInfo(metadata.ModelType))
+ {
+ yield return new DataErrorInfoClassModelValidator(metadata, context);
+ }
+
+ // If the metadata describes a property of a container that implements IDataErrorInfo,
+ // we should call its Item indexer at the appropriate time.
+ if (TypeImplementsIDataErrorInfo(metadata.ContainerType))
+ {
+ yield return new DataErrorInfoPropertyModelValidator(metadata, context);
+ }
+ }
+
+ private static bool TypeImplementsIDataErrorInfo(Type type)
+ {
+ return typeof(IDataErrorInfo).IsAssignableFrom(type);
+ }
+
+ internal sealed class DataErrorInfoClassModelValidator : ModelValidator
+ {
+ public DataErrorInfoClassModelValidator(ModelMetadata metadata, ControllerContext controllerContext)
+ : base(metadata, controllerContext)
+ {
+ }
+
+ public override IEnumerable<ModelValidationResult> Validate(object container)
+ {
+ IDataErrorInfo castModel = Metadata.Model as IDataErrorInfo;
+ if (castModel != null)
+ {
+ string errorMessage = castModel.Error;
+ if (!String.IsNullOrEmpty(errorMessage))
+ {
+ return new ModelValidationResult[]
+ {
+ new ModelValidationResult() { Message = errorMessage }
+ };
+ }
+ }
+ return Enumerable.Empty<ModelValidationResult>();
+ }
+ }
+
+ internal sealed class DataErrorInfoPropertyModelValidator : ModelValidator
+ {
+ public DataErrorInfoPropertyModelValidator(ModelMetadata metadata, ControllerContext controllerContext)
+ : base(metadata, controllerContext)
+ {
+ }
+
+ public override IEnumerable<ModelValidationResult> Validate(object container)
+ {
+ IDataErrorInfo castContainer = container as IDataErrorInfo;
+ if (castContainer != null && !String.Equals(Metadata.PropertyName, "error", StringComparison.OrdinalIgnoreCase))
+ {
+ string errorMessage = castContainer[Metadata.PropertyName];
+ if (!String.IsNullOrEmpty(errorMessage))
+ {
+ return new ModelValidationResult[]
+ {
+ new ModelValidationResult() { Message = errorMessage }
+ };
+ }
+ }
+ return Enumerable.Empty<ModelValidationResult>();
+ }
+ }
+ }
+}
diff --git a/src/System.Web.Mvc/DataTypeUtil.cs b/src/System.Web.Mvc/DataTypeUtil.cs
new file mode 100644
index 00000000..79eb9ab6
--- /dev/null
+++ b/src/System.Web.Mvc/DataTypeUtil.cs
@@ -0,0 +1,109 @@
+using System.Collections.Generic;
+using System.ComponentModel.DataAnnotations;
+
+namespace System.Web.Mvc
+{
+ internal static class DataTypeUtil
+ {
+ internal static readonly string CurrencyTypeName = DataType.Currency.ToString();
+ internal static readonly string DateTypeName = DataType.Date.ToString();
+ internal static readonly string DateTimeTypeName = DataType.DateTime.ToString();
+ internal static readonly string DurationTypeName = DataType.Duration.ToString();
+ internal static readonly string EmailAddressTypeName = DataType.EmailAddress.ToString();
+ internal static readonly string HtmlTypeName = DataType.Html.ToString();
+ internal static readonly string ImageUrlTypeName = DataType.ImageUrl.ToString();
+ internal static readonly string MultiLineTextTypeName = DataType.MultilineText.ToString();
+ internal static readonly string PasswordTypeName = DataType.Password.ToString();
+ internal static readonly string PhoneNumberTypeName = DataType.PhoneNumber.ToString();
+ internal static readonly string TextTypeName = DataType.Text.ToString();
+ internal static readonly string TimeTypeName = DataType.Time.ToString();
+ internal static readonly string UrlTypeName = DataType.Url.ToString();
+
+ private static readonly Lazy<Dictionary<object, string>> _dataTypeToName = new Lazy<Dictionary<object, string>>(CreateDataTypeToName, isThreadSafe: true);
+
+ // This is a faster version of GetDataTypeName(). It internally calls ToString() on the enum
+ // value, which can be quite slow because of value verification.
+ internal static string ToDataTypeName(this DataTypeAttribute attribute, Func<DataTypeAttribute, Boolean> isDataType = null)
+ {
+ if (isDataType == null)
+ {
+ isDataType = t => t.GetType().Equals(typeof(DataTypeAttribute));
+ }
+
+ // GetDataTypeName is virtual, so this is only safe if they haven't derived from DataTypeAttribute.
+ // However, if they derive from DataTypeAttribute, they can help their own perf by overriding GetDataTypeName
+ // and returning an appropriate string without invoking the ToString() on the enum.
+ if (isDataType(attribute))
+ {
+ // Statically known dataTypes are handled separately for performance
+ string name = KnownDataTypeToString(attribute.DataType);
+ if (name == null)
+ {
+ // Unknown types fallback to a dictionary lookup.
+ // 4.0 will not enter this code for statically known data types.
+ // 4.5 will enter this code for the new data types added to 4.5.
+ _dataTypeToName.Value.TryGetValue(attribute.DataType, out name);
+ }
+
+ if (name != null)
+ {
+ return name;
+ }
+ }
+
+ return attribute.GetDataTypeName();
+ }
+
+ private static string KnownDataTypeToString(DataType dataType)
+ {
+ switch (dataType)
+ {
+ case DataType.Currency:
+ return CurrencyTypeName;
+ case DataType.Date:
+ return DateTypeName;
+ case DataType.DateTime:
+ return DateTimeTypeName;
+ case DataType.Duration:
+ return DurationTypeName;
+ case DataType.EmailAddress:
+ return EmailAddressTypeName;
+ case DataType.Html:
+ return HtmlTypeName;
+ case DataType.ImageUrl:
+ return ImageUrlTypeName;
+ case DataType.MultilineText:
+ return MultiLineTextTypeName;
+ case DataType.Password:
+ return PasswordTypeName;
+ case DataType.PhoneNumber:
+ return PhoneNumberTypeName;
+ case DataType.Text:
+ return TextTypeName;
+ case DataType.Time:
+ return TimeTypeName;
+ case DataType.Url:
+ return UrlTypeName;
+ }
+
+ return null;
+ }
+
+ private static Dictionary<object, string> CreateDataTypeToName()
+ {
+ Dictionary<object, string> dataTypeToName = new Dictionary<object, string>();
+ foreach (DataType dataTypeValue in Enum.GetValues(typeof(DataType)))
+ {
+ // Don't add to the dictionary any of the statically known types.
+ // This is a workingset size optimization.
+ if (dataTypeValue != DataType.Custom && KnownDataTypeToString(dataTypeValue) == null)
+ {
+ string name = Enum.GetName(typeof(DataType), dataTypeValue);
+ dataTypeToName[dataTypeValue] = name;
+ }
+ }
+
+ return dataTypeToName;
+ }
+ }
+}
diff --git a/src/System.Web.Mvc/DefaultControllerFactory.cs b/src/System.Web.Mvc/DefaultControllerFactory.cs
new file mode 100644
index 00000000..8f67ca52
--- /dev/null
+++ b/src/System.Web.Mvc/DefaultControllerFactory.cs
@@ -0,0 +1,294 @@
+using System.Collections.Concurrent;
+using System.Collections.Generic;
+using System.Globalization;
+using System.Linq;
+using System.Text;
+using System.Web.Mvc.Properties;
+using System.Web.Routing;
+using System.Web.SessionState;
+
+namespace System.Web.Mvc
+{
+ public class DefaultControllerFactory : IControllerFactory
+ {
+ private static readonly ConcurrentDictionary<Type, SessionStateBehavior> _sessionStateCache = new ConcurrentDictionary<Type, SessionStateBehavior>();
+ private static ControllerTypeCache _staticControllerTypeCache = new ControllerTypeCache();
+ private IBuildManager _buildManager;
+ private IResolver<IControllerActivator> _activatorResolver;
+ private IControllerActivator _controllerActivator;
+ private ControllerBuilder _controllerBuilder;
+ private ControllerTypeCache _instanceControllerTypeCache;
+
+ public DefaultControllerFactory()
+ : this(null, null, null)
+ {
+ }
+
+ public DefaultControllerFactory(IControllerActivator controllerActivator)
+ : this(controllerActivator, null, null)
+ {
+ }
+
+ internal DefaultControllerFactory(IControllerActivator controllerActivator, IResolver<IControllerActivator> activatorResolver, IDependencyResolver dependencyResolver)
+ {
+ if (controllerActivator != null)
+ {
+ _controllerActivator = controllerActivator;
+ }
+ else
+ {
+ _activatorResolver = activatorResolver ?? new SingleServiceResolver<IControllerActivator>(
+ () => null,
+ new DefaultControllerActivator(dependencyResolver),
+ "DefaultControllerFactory constructor");
+ }
+ }
+
+ private IControllerActivator ControllerActivator
+ {
+ get
+ {
+ if (_controllerActivator != null)
+ {
+ return _controllerActivator;
+ }
+ _controllerActivator = _activatorResolver.Current;
+ return _controllerActivator;
+ }
+ }
+
+ internal IBuildManager BuildManager
+ {
+ get
+ {
+ if (_buildManager == null)
+ {
+ _buildManager = new BuildManagerWrapper();
+ }
+ return _buildManager;
+ }
+ set { _buildManager = value; }
+ }
+
+ internal ControllerBuilder ControllerBuilder
+ {
+ get { return _controllerBuilder ?? ControllerBuilder.Current; }
+ set { _controllerBuilder = value; }
+ }
+
+ internal ControllerTypeCache ControllerTypeCache
+ {
+ get { return _instanceControllerTypeCache ?? _staticControllerTypeCache; }
+ set { _instanceControllerTypeCache = value; }
+ }
+
+ internal static InvalidOperationException CreateAmbiguousControllerException(RouteBase route, string controllerName, ICollection<Type> matchingTypes)
+ {
+ // we need to generate an exception containing all the controller types
+ StringBuilder typeList = new StringBuilder();
+ foreach (Type matchedType in matchingTypes)
+ {
+ typeList.AppendLine();
+ typeList.Append(matchedType.FullName);
+ }
+
+ string errorText;
+ Route castRoute = route as Route;
+ if (castRoute != null)
+ {
+ errorText = String.Format(CultureInfo.CurrentCulture, MvcResources.DefaultControllerFactory_ControllerNameAmbiguous_WithRouteUrl,
+ controllerName, castRoute.Url, typeList);
+ }
+ else
+ {
+ errorText = String.Format(CultureInfo.CurrentCulture, MvcResources.DefaultControllerFactory_ControllerNameAmbiguous_WithoutRouteUrl,
+ controllerName, typeList);
+ }
+
+ return new InvalidOperationException(errorText);
+ }
+
+ public virtual IController CreateController(RequestContext requestContext, string controllerName)
+ {
+ if (requestContext == null)
+ {
+ throw new ArgumentNullException("requestContext");
+ }
+ if (String.IsNullOrEmpty(controllerName))
+ {
+ throw new ArgumentException(MvcResources.Common_NullOrEmpty, "controllerName");
+ }
+ Type controllerType = GetControllerType(requestContext, controllerName);
+ IController controller = GetControllerInstance(requestContext, controllerType);
+ return controller;
+ }
+
+ protected internal virtual IController GetControllerInstance(RequestContext requestContext, Type controllerType)
+ {
+ if (controllerType == null)
+ {
+ throw new HttpException(404,
+ String.Format(
+ CultureInfo.CurrentCulture,
+ MvcResources.DefaultControllerFactory_NoControllerFound,
+ requestContext.HttpContext.Request.Path));
+ }
+ if (!typeof(IController).IsAssignableFrom(controllerType))
+ {
+ throw new ArgumentException(
+ String.Format(
+ CultureInfo.CurrentCulture,
+ MvcResources.DefaultControllerFactory_TypeDoesNotSubclassControllerBase,
+ controllerType),
+ "controllerType");
+ }
+ return ControllerActivator.Create(requestContext, controllerType);
+ }
+
+ protected internal virtual SessionStateBehavior GetControllerSessionBehavior(RequestContext requestContext, Type controllerType)
+ {
+ if (controllerType == null)
+ {
+ return SessionStateBehavior.Default;
+ }
+
+ return _sessionStateCache.GetOrAdd(
+ controllerType,
+ type =>
+ {
+ var attr = type.GetCustomAttributes(typeof(SessionStateAttribute), inherit: true)
+ .OfType<SessionStateAttribute>()
+ .FirstOrDefault();
+
+ return (attr != null) ? attr.Behavior : SessionStateBehavior.Default;
+ });
+ }
+
+ protected internal virtual Type GetControllerType(RequestContext requestContext, string controllerName)
+ {
+ if (String.IsNullOrEmpty(controllerName))
+ {
+ throw new ArgumentException(MvcResources.Common_NullOrEmpty, "controllerName");
+ }
+
+ // first search in the current route's namespace collection
+ object routeNamespacesObj;
+ Type match;
+ if (requestContext != null && requestContext.RouteData.DataTokens.TryGetValue("Namespaces", out routeNamespacesObj))
+ {
+ IEnumerable<string> routeNamespaces = routeNamespacesObj as IEnumerable<string>;
+ if (routeNamespaces != null && routeNamespaces.Any())
+ {
+ HashSet<string> namespaceHash = new HashSet<string>(routeNamespaces, StringComparer.OrdinalIgnoreCase);
+ match = GetControllerTypeWithinNamespaces(requestContext.RouteData.Route, controllerName, namespaceHash);
+
+ // the UseNamespaceFallback key might not exist, in which case its value is implicitly "true"
+ if (match != null || false.Equals(requestContext.RouteData.DataTokens["UseNamespaceFallback"]))
+ {
+ // got a match or the route requested we stop looking
+ return match;
+ }
+ }
+ }
+
+ // then search in the application's default namespace collection
+ if (ControllerBuilder.DefaultNamespaces.Count > 0)
+ {
+ HashSet<string> namespaceDefaults = new HashSet<string>(ControllerBuilder.DefaultNamespaces, StringComparer.OrdinalIgnoreCase);
+ match = GetControllerTypeWithinNamespaces(requestContext.RouteData.Route, controllerName, namespaceDefaults);
+ if (match != null)
+ {
+ return match;
+ }
+ }
+
+ // if all else fails, search every namespace
+ return GetControllerTypeWithinNamespaces(requestContext.RouteData.Route, controllerName, null /* namespaces */);
+ }
+
+ private Type GetControllerTypeWithinNamespaces(RouteBase route, string controllerName, HashSet<string> namespaces)
+ {
+ // Once the master list of controllers has been created we can quickly index into it
+ ControllerTypeCache.EnsureInitialized(BuildManager);
+
+ ICollection<Type> matchingTypes = ControllerTypeCache.GetControllerTypes(controllerName, namespaces);
+ switch (matchingTypes.Count)
+ {
+ case 0:
+ // no matching types
+ return null;
+
+ case 1:
+ // single matching type
+ return matchingTypes.First();
+
+ default:
+ // multiple matching types
+ throw CreateAmbiguousControllerException(route, controllerName, matchingTypes);
+ }
+ }
+
+ public virtual void ReleaseController(IController controller)
+ {
+ IDisposable disposable = controller as IDisposable;
+ if (disposable != null)
+ {
+ disposable.Dispose();
+ }
+ }
+
+ SessionStateBehavior IControllerFactory.GetControllerSessionBehavior(RequestContext requestContext, string controllerName)
+ {
+ if (requestContext == null)
+ {
+ throw new ArgumentNullException("requestContext");
+ }
+ if (String.IsNullOrEmpty(controllerName))
+ {
+ throw new ArgumentException(MvcResources.Common_NullOrEmpty, "controllerName");
+ }
+
+ Type controllerType = GetControllerType(requestContext, controllerName);
+ return GetControllerSessionBehavior(requestContext, controllerType);
+ }
+
+ private class DefaultControllerActivator : IControllerActivator
+ {
+ private Func<IDependencyResolver> _resolverThunk;
+
+ public DefaultControllerActivator()
+ : this(null)
+ {
+ }
+
+ public DefaultControllerActivator(IDependencyResolver resolver)
+ {
+ if (resolver == null)
+ {
+ _resolverThunk = () => DependencyResolver.Current;
+ }
+ else
+ {
+ _resolverThunk = () => resolver;
+ }
+ }
+
+ public IController Create(RequestContext requestContext, Type controllerType)
+ {
+ try
+ {
+ return (IController)(_resolverThunk().GetService(controllerType) ?? Activator.CreateInstance(controllerType));
+ }
+ catch (Exception ex)
+ {
+ throw new InvalidOperationException(
+ String.Format(
+ CultureInfo.CurrentCulture,
+ MvcResources.DefaultControllerFactory_ErrorCreatingController,
+ controllerType),
+ ex);
+ }
+ }
+ }
+ }
+}
diff --git a/src/System.Web.Mvc/DefaultModelBinder.cs b/src/System.Web.Mvc/DefaultModelBinder.cs
new file mode 100644
index 00000000..6a0377e9
--- /dev/null
+++ b/src/System.Web.Mvc/DefaultModelBinder.cs
@@ -0,0 +1,840 @@
+using System.Collections;
+using System.Collections.Generic;
+using System.ComponentModel;
+using System.Diagnostics.CodeAnalysis;
+using System.Globalization;
+using System.Linq;
+using System.Reflection;
+using System.Runtime.CompilerServices;
+using System.Web.Mvc.Properties;
+
+namespace System.Web.Mvc
+{
+ public class DefaultModelBinder : IModelBinder
+ {
+ private static string _resourceClassKey;
+ private ModelBinderDictionary _binders;
+
+ [SuppressMessage("Microsoft.Usage", "CA2227:CollectionPropertiesShouldBeReadOnly", Justification = "Property is settable so that the dictionary can be provided for unit testing purposes.")]
+ protected internal ModelBinderDictionary Binders
+ {
+ get
+ {
+ if (_binders == null)
+ {
+ _binders = ModelBinders.Binders;
+ }
+ return _binders;
+ }
+ set { _binders = value; }
+ }
+
+ public static string ResourceClassKey
+ {
+ get { return _resourceClassKey ?? String.Empty; }
+ set { _resourceClassKey = value; }
+ }
+
+ private static void AddValueRequiredMessageToModelState(ControllerContext controllerContext, ModelStateDictionary modelState, string modelStateKey, Type elementType, object value)
+ {
+ if (value == null && !TypeHelpers.TypeAllowsNullValue(elementType) && modelState.IsValidField(modelStateKey))
+ {
+ modelState.AddModelError(modelStateKey, GetValueRequiredResource(controllerContext));
+ }
+ }
+
+ internal void BindComplexElementalModel(ControllerContext controllerContext, ModelBindingContext bindingContext, object model)
+ {
+ // need to replace the property filter + model object and create an inner binding context
+ ModelBindingContext newBindingContext = CreateComplexElementalModelBindingContext(controllerContext, bindingContext, model);
+
+ // validation
+ if (OnModelUpdating(controllerContext, newBindingContext))
+ {
+ BindProperties(controllerContext, newBindingContext);
+ OnModelUpdated(controllerContext, newBindingContext);
+ }
+ }
+
+ internal object BindComplexModel(ControllerContext controllerContext, ModelBindingContext bindingContext)
+ {
+ object model = bindingContext.Model;
+ Type modelType = bindingContext.ModelType;
+
+ // if we're being asked to create an array, create a list instead, then coerce to an array after the list is created
+ if (model == null && modelType.IsArray)
+ {
+ Type elementType = modelType.GetElementType();
+ Type listType = typeof(List<>).MakeGenericType(elementType);
+ object collection = CreateModel(controllerContext, bindingContext, listType);
+
+ ModelBindingContext arrayBindingContext = new ModelBindingContext()
+ {
+ ModelMetadata = ModelMetadataProviders.Current.GetMetadataForType(() => collection, listType),
+ ModelName = bindingContext.ModelName,
+ ModelState = bindingContext.ModelState,
+ PropertyFilter = bindingContext.PropertyFilter,
+ ValueProvider = bindingContext.ValueProvider
+ };
+ IList list = (IList)UpdateCollection(controllerContext, arrayBindingContext, elementType);
+
+ if (list == null)
+ {
+ return null;
+ }
+
+ Array array = Array.CreateInstance(elementType, list.Count);
+ list.CopyTo(array, 0);
+ return array;
+ }
+
+ if (model == null)
+ {
+ model = CreateModel(controllerContext, bindingContext, modelType);
+ }
+
+ // special-case IDictionary<,> and ICollection<>
+ Type dictionaryType = TypeHelpers.ExtractGenericInterface(modelType, typeof(IDictionary<,>));
+ if (dictionaryType != null)
+ {
+ Type[] genericArguments = dictionaryType.GetGenericArguments();
+ Type keyType = genericArguments[0];
+ Type valueType = genericArguments[1];
+
+ ModelBindingContext dictionaryBindingContext = new ModelBindingContext()
+ {
+ ModelMetadata = ModelMetadataProviders.Current.GetMetadataForType(() => model, modelType),
+ ModelName = bindingContext.ModelName,
+ ModelState = bindingContext.ModelState,
+ PropertyFilter = bindingContext.PropertyFilter,
+ ValueProvider = bindingContext.ValueProvider
+ };
+ object dictionary = UpdateDictionary(controllerContext, dictionaryBindingContext, keyType, valueType);
+ return dictionary;
+ }
+
+ Type enumerableType = TypeHelpers.ExtractGenericInterface(modelType, typeof(IEnumerable<>));
+ if (enumerableType != null)
+ {
+ Type elementType = enumerableType.GetGenericArguments()[0];
+
+ Type collectionType = typeof(ICollection<>).MakeGenericType(elementType);
+ if (collectionType.IsInstanceOfType(model))
+ {
+ ModelBindingContext collectionBindingContext = new ModelBindingContext()
+ {
+ ModelMetadata = ModelMetadataProviders.Current.GetMetadataForType(() => model, modelType),
+ ModelName = bindingContext.ModelName,
+ ModelState = bindingContext.ModelState,
+ PropertyFilter = bindingContext.PropertyFilter,
+ ValueProvider = bindingContext.ValueProvider
+ };
+ object collection = UpdateCollection(controllerContext, collectionBindingContext, elementType);
+ return collection;
+ }
+ }
+
+ // otherwise, just update the properties on the complex type
+ BindComplexElementalModel(controllerContext, bindingContext, model);
+ return model;
+ }
+
+ public virtual object BindModel(ControllerContext controllerContext, ModelBindingContext bindingContext)
+ {
+ if (bindingContext == null)
+ {
+ throw new ArgumentNullException("bindingContext");
+ }
+
+ bool performedFallback = false;
+
+ if (!String.IsNullOrEmpty(bindingContext.ModelName) && !bindingContext.ValueProvider.ContainsPrefix(bindingContext.ModelName))
+ {
+ // We couldn't find any entry that began with the prefix. If this is the top-level element, fall back
+ // to the empty prefix.
+ if (bindingContext.FallbackToEmptyPrefix)
+ {
+ bindingContext = new ModelBindingContext()
+ {
+ ModelMetadata = bindingContext.ModelMetadata,
+ ModelState = bindingContext.ModelState,
+ PropertyFilter = bindingContext.PropertyFilter,
+ ValueProvider = bindingContext.ValueProvider
+ };
+ performedFallback = true;
+ }
+ else
+ {
+ return null;
+ }
+ }
+
+ // Simple model = int, string, etc.; determined by calling TypeConverter.CanConvertFrom(typeof(string))
+ // or by seeing if a value in the request exactly matches the name of the model we're binding.
+ // Complex type = everything else.
+ if (!performedFallback)
+ {
+ bool performRequestValidation = ShouldPerformRequestValidation(controllerContext, bindingContext);
+ ValueProviderResult valueProviderResult = bindingContext.UnvalidatedValueProvider.GetValue(bindingContext.ModelName, skipValidation: !performRequestValidation);
+ if (valueProviderResult != null)
+ {
+ return BindSimpleModel(controllerContext, bindingContext, valueProviderResult);
+ }
+ }
+ if (!bindingContext.ModelMetadata.IsComplexType)
+ {
+ return null;
+ }
+
+ return BindComplexModel(controllerContext, bindingContext);
+ }
+
+ private void BindProperties(ControllerContext controllerContext, ModelBindingContext bindingContext)
+ {
+ IEnumerable<PropertyDescriptor> properties = GetFilteredModelProperties(controllerContext, bindingContext);
+ foreach (PropertyDescriptor property in properties)
+ {
+ BindProperty(controllerContext, bindingContext, property);
+ }
+ }
+
+ protected virtual void BindProperty(ControllerContext controllerContext, ModelBindingContext bindingContext, PropertyDescriptor propertyDescriptor)
+ {
+ // need to skip properties that aren't part of the request, else we might hit a StackOverflowException
+ string fullPropertyKey = CreateSubPropertyName(bindingContext.ModelName, propertyDescriptor.Name);
+ if (!bindingContext.ValueProvider.ContainsPrefix(fullPropertyKey))
+ {
+ return;
+ }
+
+ // call into the property's model binder
+ IModelBinder propertyBinder = Binders.GetBinder(propertyDescriptor.PropertyType);
+ object originalPropertyValue = propertyDescriptor.GetValue(bindingContext.Model);
+ ModelMetadata propertyMetadata = bindingContext.PropertyMetadata[propertyDescriptor.Name];
+ propertyMetadata.Model = originalPropertyValue;
+ ModelBindingContext innerBindingContext = new ModelBindingContext()
+ {
+ ModelMetadata = propertyMetadata,
+ ModelName = fullPropertyKey,
+ ModelState = bindingContext.ModelState,
+ ValueProvider = bindingContext.ValueProvider
+ };
+ object newPropertyValue = GetPropertyValue(controllerContext, innerBindingContext, propertyDescriptor, propertyBinder);
+ propertyMetadata.Model = newPropertyValue;
+
+ // validation
+ ModelState modelState = bindingContext.ModelState[fullPropertyKey];
+ if (modelState == null || modelState.Errors.Count == 0)
+ {
+ if (OnPropertyValidating(controllerContext, bindingContext, propertyDescriptor, newPropertyValue))
+ {
+ SetProperty(controllerContext, bindingContext, propertyDescriptor, newPropertyValue);
+ OnPropertyValidated(controllerContext, bindingContext, propertyDescriptor, newPropertyValue);
+ }
+ }
+ else
+ {
+ SetProperty(controllerContext, bindingContext, propertyDescriptor, newPropertyValue);
+
+ // Convert FormatExceptions (type conversion failures) into InvalidValue messages
+ foreach (ModelError error in modelState.Errors.Where(err => String.IsNullOrEmpty(err.ErrorMessage) && err.Exception != null).ToList())
+ {
+ for (Exception exception = error.Exception; exception != null; exception = exception.InnerException)
+ {
+ if (exception is FormatException)
+ {
+ string displayName = propertyMetadata.GetDisplayName();
+ string errorMessageTemplate = GetValueInvalidResource(controllerContext);
+ string errorMessage = String.Format(CultureInfo.CurrentCulture, errorMessageTemplate, modelState.Value.AttemptedValue, displayName);
+ modelState.Errors.Remove(error);
+ modelState.Errors.Add(errorMessage);
+ break;
+ }
+ }
+ }
+ }
+ }
+
+ internal object BindSimpleModel(ControllerContext controllerContext, ModelBindingContext bindingContext, ValueProviderResult valueProviderResult)
+ {
+ bindingContext.ModelState.SetModelValue(bindingContext.ModelName, valueProviderResult);
+
+ // if the value provider returns an instance of the requested data type, we can just short-circuit
+ // the evaluation and return that instance
+ if (bindingContext.ModelType.IsInstanceOfType(valueProviderResult.RawValue))
+ {
+ return valueProviderResult.RawValue;
+ }
+
+ // since a string is an IEnumerable<char>, we want it to skip the two checks immediately following
+ if (bindingContext.ModelType != typeof(string))
+ {
+ // conversion results in 3 cases, as below
+ if (bindingContext.ModelType.IsArray)
+ {
+ // case 1: user asked for an array
+ // ValueProviderResult.ConvertTo() understands array types, so pass in the array type directly
+ object modelArray = ConvertProviderResult(bindingContext.ModelState, bindingContext.ModelName, valueProviderResult, bindingContext.ModelType);
+ return modelArray;
+ }
+
+ Type enumerableType = TypeHelpers.ExtractGenericInterface(bindingContext.ModelType, typeof(IEnumerable<>));
+ if (enumerableType != null)
+ {
+ // case 2: user asked for a collection rather than an array
+ // need to call ConvertTo() on the array type, then copy the array to the collection
+ object modelCollection = CreateModel(controllerContext, bindingContext, bindingContext.ModelType);
+ Type elementType = enumerableType.GetGenericArguments()[0];
+ Type arrayType = elementType.MakeArrayType();
+ object modelArray = ConvertProviderResult(bindingContext.ModelState, bindingContext.ModelName, valueProviderResult, arrayType);
+
+ Type collectionType = typeof(ICollection<>).MakeGenericType(elementType);
+ if (collectionType.IsInstanceOfType(modelCollection))
+ {
+ CollectionHelpers.ReplaceCollection(elementType, modelCollection, modelArray);
+ }
+ return modelCollection;
+ }
+ }
+
+ // case 3: user asked for an individual element
+ object model = ConvertProviderResult(bindingContext.ModelState, bindingContext.ModelName, valueProviderResult, bindingContext.ModelType);
+ return model;
+ }
+
+ private static bool CanUpdateReadonlyTypedReference(Type type)
+ {
+ // value types aren't strictly immutable, but because they have copy-by-value semantics
+ // we can't update a value type that is marked readonly
+ if (type.IsValueType)
+ {
+ return false;
+ }
+
+ // arrays are mutable, but because we can't change their length we shouldn't try
+ // to update an array that is referenced readonly
+ if (type.IsArray)
+ {
+ return false;
+ }
+
+ // special-case known common immutable types
+ if (type == typeof(string))
+ {
+ return false;
+ }
+
+ return true;
+ }
+
+ [SuppressMessage("Microsoft.Globalization", "CA1304:SpecifyCultureInfo", MessageId = "System.Web.Mvc.ValueProviderResult.ConvertTo(System.Type)", Justification = "The target object should make the correct culture determination, not this method.")]
+ [SuppressMessage("Microsoft.Design", "CA1031:DoNotCatchGeneralExceptionTypes", Justification = "We're recording this exception so that we can act on it later.")]
+ private static object ConvertProviderResult(ModelStateDictionary modelState, string modelStateKey, ValueProviderResult valueProviderResult, Type destinationType)
+ {
+ try
+ {
+ object convertedValue = valueProviderResult.ConvertTo(destinationType);
+ return convertedValue;
+ }
+ catch (Exception ex)
+ {
+ modelState.AddModelError(modelStateKey, ex);
+ return null;
+ }
+ }
+
+ internal ModelBindingContext CreateComplexElementalModelBindingContext(ControllerContext controllerContext, ModelBindingContext bindingContext, object model)
+ {
+ BindAttribute bindAttr = (BindAttribute)GetTypeDescriptor(controllerContext, bindingContext).GetAttributes()[typeof(BindAttribute)];
+ Predicate<string> newPropertyFilter = (bindAttr != null)
+ ? propertyName => bindAttr.IsPropertyAllowed(propertyName) && bindingContext.PropertyFilter(propertyName)
+ : bindingContext.PropertyFilter;
+
+ ModelBindingContext newBindingContext = new ModelBindingContext()
+ {
+ ModelMetadata = ModelMetadataProviders.Current.GetMetadataForType(() => model, bindingContext.ModelType),
+ ModelName = bindingContext.ModelName,
+ ModelState = bindingContext.ModelState,
+ PropertyFilter = newPropertyFilter,
+ ValueProvider = bindingContext.ValueProvider
+ };
+
+ return newBindingContext;
+ }
+
+ protected virtual object CreateModel(ControllerContext controllerContext, ModelBindingContext bindingContext, Type modelType)
+ {
+ Type typeToCreate = modelType;
+
+ // we can understand some collection interfaces, e.g. IList<>, IDictionary<,>
+ if (modelType.IsGenericType)
+ {
+ Type genericTypeDefinition = modelType.GetGenericTypeDefinition();
+ if (genericTypeDefinition == typeof(IDictionary<,>))
+ {
+ typeToCreate = typeof(Dictionary<,>).MakeGenericType(modelType.GetGenericArguments());
+ }
+ else if (genericTypeDefinition == typeof(IEnumerable<>) || genericTypeDefinition == typeof(ICollection<>) || genericTypeDefinition == typeof(IList<>))
+ {
+ typeToCreate = typeof(List<>).MakeGenericType(modelType.GetGenericArguments());
+ }
+ }
+
+ // fallback to the type's default constructor
+ return Activator.CreateInstance(typeToCreate);
+ }
+
+ protected static string CreateSubIndexName(string prefix, int index)
+ {
+ return String.Format(CultureInfo.InvariantCulture, "{0}[{1}]", prefix, index);
+ }
+
+ protected static string CreateSubIndexName(string prefix, string index)
+ {
+ return String.Format(CultureInfo.InvariantCulture, "{0}[{1}]", prefix, index);
+ }
+
+ protected internal static string CreateSubPropertyName(string prefix, string propertyName)
+ {
+ if (String.IsNullOrEmpty(prefix))
+ {
+ return propertyName;
+ }
+ else if (String.IsNullOrEmpty(propertyName))
+ {
+ return prefix;
+ }
+ else
+ {
+ return prefix + "." + propertyName;
+ }
+ }
+
+ protected IEnumerable<PropertyDescriptor> GetFilteredModelProperties(ControllerContext controllerContext, ModelBindingContext bindingContext)
+ {
+ PropertyDescriptorCollection properties = GetModelProperties(controllerContext, bindingContext);
+ Predicate<string> propertyFilter = bindingContext.PropertyFilter;
+
+ return from PropertyDescriptor property in properties
+ where ShouldUpdateProperty(property, propertyFilter)
+ select property;
+ }
+
+ [SuppressMessage("Microsoft.Globalization", "CA1304:SpecifyCultureInfo", MessageId = "System.Web.Mvc.ValueProviderResult.ConvertTo(System.Type)", Justification = "ValueProviderResult already handles culture conversion appropriately.")]
+ private static void GetIndexes(ModelBindingContext bindingContext, out bool stopOnIndexNotFound, out IEnumerable<string> indexes)
+ {
+ string indexKey = CreateSubPropertyName(bindingContext.ModelName, "index");
+ ValueProviderResult valueProviderResult = bindingContext.ValueProvider.GetValue(indexKey);
+
+ if (valueProviderResult != null)
+ {
+ string[] indexesArray = valueProviderResult.ConvertTo(typeof(string[])) as string[];
+ if (indexesArray != null)
+ {
+ stopOnIndexNotFound = false;
+ indexes = indexesArray;
+ return;
+ }
+ }
+
+ // just use a simple zero-based system
+ stopOnIndexNotFound = true;
+ indexes = GetZeroBasedIndexes();
+ }
+
+ protected virtual PropertyDescriptorCollection GetModelProperties(ControllerContext controllerContext, ModelBindingContext bindingContext)
+ {
+ return GetTypeDescriptor(controllerContext, bindingContext).GetProperties();
+ }
+
+ protected virtual object GetPropertyValue(ControllerContext controllerContext, ModelBindingContext bindingContext, PropertyDescriptor propertyDescriptor, IModelBinder propertyBinder)
+ {
+ object value = propertyBinder.BindModel(controllerContext, bindingContext);
+
+ if (bindingContext.ModelMetadata.ConvertEmptyStringToNull && Equals(value, String.Empty))
+ {
+ return null;
+ }
+
+ return value;
+ }
+
+ protected virtual ICustomTypeDescriptor GetTypeDescriptor(ControllerContext controllerContext, ModelBindingContext bindingContext)
+ {
+ return TypeDescriptorHelper.Get(bindingContext.ModelType);
+ }
+
+ // If the user specified a ResourceClassKey try to load the resource they specified.
+ // If the class key is invalid, an exception will be thrown.
+ // If the class key is valid but the resource is not found, it returns null, in which
+ // case it will fall back to the MVC default error message.
+ private static string GetUserResourceString(ControllerContext controllerContext, string resourceName)
+ {
+ string result = null;
+
+ if (!String.IsNullOrEmpty(ResourceClassKey) && (controllerContext != null) && (controllerContext.HttpContext != null))
+ {
+ result = controllerContext.HttpContext.GetGlobalResourceObject(ResourceClassKey, resourceName, CultureInfo.CurrentUICulture) as string;
+ }
+
+ return result;
+ }
+
+ private static string GetValueInvalidResource(ControllerContext controllerContext)
+ {
+ return GetUserResourceString(controllerContext, "PropertyValueInvalid") ?? MvcResources.DefaultModelBinder_ValueInvalid;
+ }
+
+ private static string GetValueRequiredResource(ControllerContext controllerContext)
+ {
+ return GetUserResourceString(controllerContext, "PropertyValueRequired") ?? MvcResources.DefaultModelBinder_ValueRequired;
+ }
+
+ private static IEnumerable<string> GetZeroBasedIndexes()
+ {
+ int i = 0;
+ while (true)
+ {
+ yield return i.ToString(CultureInfo.InvariantCulture);
+ i++;
+ }
+ }
+
+ protected static bool IsModelValid(ModelBindingContext bindingContext)
+ {
+ if (bindingContext == null)
+ {
+ throw new ArgumentNullException("bindingContext");
+ }
+ if (String.IsNullOrEmpty(bindingContext.ModelName))
+ {
+ return bindingContext.ModelState.IsValid;
+ }
+ return bindingContext.ModelState.IsValidField(bindingContext.ModelName);
+ }
+
+ protected virtual void OnModelUpdated(ControllerContext controllerContext, ModelBindingContext bindingContext)
+ {
+ Dictionary<string, bool> startedValid = new Dictionary<string, bool>(StringComparer.OrdinalIgnoreCase);
+
+ foreach (ModelValidationResult validationResult in ModelValidator.GetModelValidator(bindingContext.ModelMetadata, controllerContext).Validate(null))
+ {
+ string subPropertyName = CreateSubPropertyName(bindingContext.ModelName, validationResult.MemberName);
+
+ if (!startedValid.ContainsKey(subPropertyName))
+ {
+ startedValid[subPropertyName] = bindingContext.ModelState.IsValidField(subPropertyName);
+ }
+
+ if (startedValid[subPropertyName])
+ {
+ bindingContext.ModelState.AddModelError(subPropertyName, validationResult.Message);
+ }
+ }
+ }
+
+ protected virtual bool OnModelUpdating(ControllerContext controllerContext, ModelBindingContext bindingContext)
+ {
+ // default implementation does nothing
+ return true;
+ }
+
+ protected virtual void OnPropertyValidated(ControllerContext controllerContext, ModelBindingContext bindingContext, PropertyDescriptor propertyDescriptor, object value)
+ {
+ // default implementation does nothing
+ }
+
+ protected virtual bool OnPropertyValidating(ControllerContext controllerContext, ModelBindingContext bindingContext, PropertyDescriptor propertyDescriptor, object value)
+ {
+ // default implementation does nothing
+ return true;
+ }
+
+ [SuppressMessage("Microsoft.Design", "CA1031:DoNotCatchGeneralExceptionTypes", Justification = "We're recording this exception so that we can act on it later.")]
+ protected virtual void SetProperty(ControllerContext controllerContext, ModelBindingContext bindingContext, PropertyDescriptor propertyDescriptor, object value)
+ {
+ ModelMetadata propertyMetadata = bindingContext.PropertyMetadata[propertyDescriptor.Name];
+ propertyMetadata.Model = value;
+ string modelStateKey = CreateSubPropertyName(bindingContext.ModelName, propertyMetadata.PropertyName);
+
+ // If the value is null, and the validation system can find a Required validator for
+ // us, we'd prefer to run it before we attempt to set the value; otherwise, property
+ // setters which throw on null (f.e., Entity Framework properties which are backed by
+ // non-nullable strings in the DB) will get their error message in ahead of us.
+ //
+ // We are effectively using the special validator -- Required -- as a helper to the
+ // binding system, which is why this code is here instead of in the Validating/Validated
+ // methods, which are really the old-school validation hooks.
+ if (value == null && bindingContext.ModelState.IsValidField(modelStateKey))
+ {
+ ModelValidator requiredValidator = ModelValidatorProviders.Providers.GetValidators(propertyMetadata, controllerContext).Where(v => v.IsRequired).FirstOrDefault();
+ if (requiredValidator != null)
+ {
+ foreach (ModelValidationResult validationResult in requiredValidator.Validate(bindingContext.Model))
+ {
+ bindingContext.ModelState.AddModelError(modelStateKey, validationResult.Message);
+ }
+ }
+ }
+
+ bool isNullValueOnNonNullableType =
+ value == null &&
+ !TypeHelpers.TypeAllowsNullValue(propertyDescriptor.PropertyType);
+
+ // Try to set a value into the property unless we know it will fail (read-only
+ // properties and null values with non-nullable types)
+ if (!propertyDescriptor.IsReadOnly && !isNullValueOnNonNullableType)
+ {
+ try
+ {
+ propertyDescriptor.SetValue(bindingContext.Model, value);
+ }
+ catch (Exception ex)
+ {
+ // Only add if we're not already invalid
+ if (bindingContext.ModelState.IsValidField(modelStateKey))
+ {
+ bindingContext.ModelState.AddModelError(modelStateKey, ex);
+ }
+ }
+ }
+
+ // Last chance for an error on null values with non-nullable types, we'll use
+ // the default "A value is required." message.
+ if (isNullValueOnNonNullableType && bindingContext.ModelState.IsValidField(modelStateKey))
+ {
+ bindingContext.ModelState.AddModelError(modelStateKey, GetValueRequiredResource(controllerContext));
+ }
+ }
+
+ private static bool ShouldPerformRequestValidation(ControllerContext controllerContext, ModelBindingContext bindingContext)
+ {
+ if (controllerContext == null || controllerContext.Controller == null || bindingContext == null || bindingContext.ModelMetadata == null)
+ {
+ // To make unit testing easier, if the caller hasn't specified enough contextual information we just default
+ // to always pulling the data from a collection that goes through request validation.
+ return true;
+ }
+
+ // We should perform request validation only if both the controller and the model ask for it. This is the
+ // default behavior for both. If either the controller (via [ValidateInput(false)]) or the model (via [AllowHtml])
+ // opts out, we don't validate.
+ return (controllerContext.Controller.ValidateRequest && bindingContext.ModelMetadata.RequestValidationEnabled);
+ }
+
+ private static bool ShouldUpdateProperty(PropertyDescriptor property, Predicate<string> propertyFilter)
+ {
+ if (property.IsReadOnly && !CanUpdateReadonlyTypedReference(property.PropertyType))
+ {
+ return false;
+ }
+
+ // if this property is rejected by the filter, move on
+ if (!propertyFilter(property.Name))
+ {
+ return false;
+ }
+
+ // otherwise, allow
+ return true;
+ }
+
+ internal object UpdateCollection(ControllerContext controllerContext, ModelBindingContext bindingContext, Type elementType)
+ {
+ bool stopOnIndexNotFound;
+ IEnumerable<string> indexes;
+ GetIndexes(bindingContext, out stopOnIndexNotFound, out indexes);
+ IModelBinder elementBinder = Binders.GetBinder(elementType);
+
+ // build up a list of items from the request
+ List<object> modelList = new List<object>();
+ foreach (string currentIndex in indexes)
+ {
+ string subIndexKey = CreateSubIndexName(bindingContext.ModelName, currentIndex);
+ if (!bindingContext.ValueProvider.ContainsPrefix(subIndexKey))
+ {
+ if (stopOnIndexNotFound)
+ {
+ // we ran out of elements to pull
+ break;
+ }
+ else
+ {
+ continue;
+ }
+ }
+
+ ModelBindingContext innerContext = new ModelBindingContext()
+ {
+ ModelMetadata = ModelMetadataProviders.Current.GetMetadataForType(null, elementType),
+ ModelName = subIndexKey,
+ ModelState = bindingContext.ModelState,
+ PropertyFilter = bindingContext.PropertyFilter,
+ ValueProvider = bindingContext.ValueProvider
+ };
+ object thisElement = elementBinder.BindModel(controllerContext, innerContext);
+
+ // we need to merge model errors up
+ AddValueRequiredMessageToModelState(controllerContext, bindingContext.ModelState, subIndexKey, elementType, thisElement);
+ modelList.Add(thisElement);
+ }
+
+ // if there weren't any elements at all in the request, just return
+ if (modelList.Count == 0)
+ {
+ return null;
+ }
+
+ // replace the original collection
+ object collection = bindingContext.Model;
+ CollectionHelpers.ReplaceCollection(elementType, collection, modelList);
+ return collection;
+ }
+
+ internal object UpdateDictionary(ControllerContext controllerContext, ModelBindingContext bindingContext, Type keyType, Type valueType)
+ {
+ bool stopOnIndexNotFound;
+ IEnumerable<string> indexes;
+ GetIndexes(bindingContext, out stopOnIndexNotFound, out indexes);
+
+ IModelBinder keyBinder = Binders.GetBinder(keyType);
+ IModelBinder valueBinder = Binders.GetBinder(valueType);
+
+ // build up a list of items from the request
+ List<KeyValuePair<object, object>> modelList = new List<KeyValuePair<object, object>>();
+ foreach (string currentIndex in indexes)
+ {
+ string subIndexKey = CreateSubIndexName(bindingContext.ModelName, currentIndex);
+ string keyFieldKey = CreateSubPropertyName(subIndexKey, "key");
+ string valueFieldKey = CreateSubPropertyName(subIndexKey, "value");
+
+ if (!(bindingContext.ValueProvider.ContainsPrefix(keyFieldKey) && bindingContext.ValueProvider.ContainsPrefix(valueFieldKey)))
+ {
+ if (stopOnIndexNotFound)
+ {
+ // we ran out of elements to pull
+ break;
+ }
+ else
+ {
+ continue;
+ }
+ }
+
+ // bind the key
+ ModelBindingContext keyBindingContext = new ModelBindingContext()
+ {
+ ModelMetadata = ModelMetadataProviders.Current.GetMetadataForType(null, keyType),
+ ModelName = keyFieldKey,
+ ModelState = bindingContext.ModelState,
+ ValueProvider = bindingContext.ValueProvider
+ };
+ object thisKey = keyBinder.BindModel(controllerContext, keyBindingContext);
+
+ // we need to merge model errors up
+ AddValueRequiredMessageToModelState(controllerContext, bindingContext.ModelState, keyFieldKey, keyType, thisKey);
+ if (!keyType.IsInstanceOfType(thisKey))
+ {
+ // we can't add an invalid key, so just move on
+ continue;
+ }
+
+ // bind the value
+ modelList.Add(CreateEntryForModel(controllerContext, bindingContext, valueType, valueBinder, valueFieldKey, thisKey));
+ }
+
+ // Let's try another method
+ if (modelList.Count == 0)
+ {
+ IEnumerableValueProvider enumerableValueProvider = bindingContext.ValueProvider as IEnumerableValueProvider;
+ if (enumerableValueProvider != null)
+ {
+ IDictionary<string, string> keys = enumerableValueProvider.GetKeysFromPrefix(bindingContext.ModelName);
+ foreach (var thisKey in keys)
+ {
+ modelList.Add(CreateEntryForModel(controllerContext, bindingContext, valueType, valueBinder, thisKey.Value, thisKey.Key));
+ }
+ }
+ }
+
+ // if there weren't any elements at all in the request, just return
+ if (modelList.Count == 0)
+ {
+ return null;
+ }
+
+ // replace the original collection
+ object dictionary = bindingContext.Model;
+ CollectionHelpers.ReplaceDictionary(keyType, valueType, dictionary, modelList);
+ return dictionary;
+ }
+
+ private static KeyValuePair<object, object> CreateEntryForModel(ControllerContext controllerContext, ModelBindingContext bindingContext, Type valueType, IModelBinder valueBinder, string modelName, object modelKey)
+ {
+ ModelBindingContext valueBindingContext = new ModelBindingContext()
+ {
+ ModelMetadata = ModelMetadataProviders.Current.GetMetadataForType(null, valueType),
+ ModelName = modelName,
+ ModelState = bindingContext.ModelState,
+ PropertyFilter = bindingContext.PropertyFilter,
+ ValueProvider = bindingContext.ValueProvider
+ };
+ object thisValue = valueBinder.BindModel(controllerContext, valueBindingContext);
+ AddValueRequiredMessageToModelState(controllerContext, bindingContext.ModelState, modelName, valueType, thisValue);
+ return new KeyValuePair<object, object>(modelKey, thisValue);
+ }
+
+ // This helper type is used because we're working with strongly-typed collections, but we don't know the Ts
+ // ahead of time. By using the generic methods below, we can consolidate the collection-specific code in a
+ // single helper type rather than having reflection-based calls spread throughout the DefaultModelBinder type.
+ // There is a single point of entry to each of the methods below, so they're fairly simple to maintain.
+
+ private static class CollectionHelpers
+ {
+ private static readonly MethodInfo _replaceCollectionMethod = typeof(CollectionHelpers).GetMethod("ReplaceCollectionImpl", BindingFlags.Static | BindingFlags.NonPublic);
+ private static readonly MethodInfo _replaceDictionaryMethod = typeof(CollectionHelpers).GetMethod("ReplaceDictionaryImpl", BindingFlags.Static | BindingFlags.NonPublic);
+
+ [MethodImpl(MethodImplOptions.NoInlining | MethodImplOptions.NoOptimization)]
+ public static void ReplaceCollection(Type collectionType, object collection, object newContents)
+ {
+ MethodInfo targetMethod = _replaceCollectionMethod.MakeGenericMethod(collectionType);
+ targetMethod.Invoke(null, new object[] { collection, newContents });
+ }
+
+ private static void ReplaceCollectionImpl<T>(ICollection<T> collection, IEnumerable newContents)
+ {
+ collection.Clear();
+ if (newContents != null)
+ {
+ foreach (object item in newContents)
+ {
+ // if the item was not a T, some conversion failed. the error message will be propagated,
+ // but in the meanwhile we need to make a placeholder element in the array.
+ T castItem = (item is T) ? (T)item : default(T);
+ collection.Add(castItem);
+ }
+ }
+ }
+
+ [MethodImpl(MethodImplOptions.NoInlining | MethodImplOptions.NoOptimization)]
+ public static void ReplaceDictionary(Type keyType, Type valueType, object dictionary, object newContents)
+ {
+ MethodInfo targetMethod = _replaceDictionaryMethod.MakeGenericMethod(keyType, valueType);
+ targetMethod.Invoke(null, new object[] { dictionary, newContents });
+ }
+
+ private static void ReplaceDictionaryImpl<TKey, TValue>(IDictionary<TKey, TValue> dictionary, IEnumerable<KeyValuePair<object, object>> newContents)
+ {
+ dictionary.Clear();
+ foreach (KeyValuePair<object, object> item in newContents)
+ {
+ // if the item was not a T, some conversion failed. the error message will be propagated,
+ // but in the meanwhile we need to make a placeholder element in the dictionary.
+ TKey castKey = (TKey)item.Key; // this cast shouldn't fail
+ TValue castValue = (item.Value is TValue) ? (TValue)item.Value : default(TValue);
+ dictionary[castKey] = castValue;
+ }
+ }
+ }
+ }
+}
diff --git a/src/System.Web.Mvc/DefaultViewLocationCache.cs b/src/System.Web.Mvc/DefaultViewLocationCache.cs
new file mode 100644
index 00000000..3e6090f6
--- /dev/null
+++ b/src/System.Web.Mvc/DefaultViewLocationCache.cs
@@ -0,0 +1,52 @@
+using System.Diagnostics.CodeAnalysis;
+using System.Web.Caching;
+using System.Web.Mvc.Properties;
+
+namespace System.Web.Mvc
+{
+ public class DefaultViewLocationCache : IViewLocationCache
+ {
+ private static readonly TimeSpan _defaultTimeSpan = new TimeSpan(0, 15, 0);
+
+ [SuppressMessage("Microsoft.Security", "CA2104:DoNotDeclareReadOnlyMutableReferenceTypes", Justification = "The reference type is immutable. ")]
+ public static readonly IViewLocationCache Null = new NullViewLocationCache();
+
+ public DefaultViewLocationCache()
+ : this(_defaultTimeSpan)
+ {
+ }
+
+ public DefaultViewLocationCache(TimeSpan timeSpan)
+ {
+ if (timeSpan.Ticks < 0)
+ {
+ throw new InvalidOperationException(MvcResources.DefaultViewLocationCache_NegativeTimeSpan);
+ }
+ TimeSpan = timeSpan;
+ }
+
+ public TimeSpan TimeSpan { get; private set; }
+
+ #region IViewLocationCache Members
+
+ public string GetViewLocation(HttpContextBase httpContext, string key)
+ {
+ if (httpContext == null)
+ {
+ throw new ArgumentNullException("httpContext");
+ }
+ return (string)httpContext.Cache[key];
+ }
+
+ public void InsertViewLocation(HttpContextBase httpContext, string key, string virtualPath)
+ {
+ if (httpContext == null)
+ {
+ throw new ArgumentNullException("httpContext");
+ }
+ httpContext.Cache.Insert(key, virtualPath, null /* dependencies */, Cache.NoAbsoluteExpiration, TimeSpan);
+ }
+
+ #endregion
+ }
+}
diff --git a/src/System.Web.Mvc/DependencyResolver.cs b/src/System.Web.Mvc/DependencyResolver.cs
new file mode 100644
index 00000000..6fdb0807
--- /dev/null
+++ b/src/System.Web.Mvc/DependencyResolver.cs
@@ -0,0 +1,210 @@
+using System.Collections.Concurrent;
+using System.Collections.Generic;
+using System.Diagnostics.CodeAnalysis;
+using System.Globalization;
+using System.Linq;
+using System.Reflection;
+using System.Web.Mvc.Properties;
+
+namespace System.Web.Mvc
+{
+ public class DependencyResolver
+ {
+ private static DependencyResolver _instance = new DependencyResolver();
+
+ private IDependencyResolver _current;
+
+ /// <summary>
+ /// Cache should always be a new CacheDependencyResolver(_current).
+ /// </summary>
+ private CacheDependencyResolver _currentCache;
+
+ public DependencyResolver()
+ {
+ InnerSetResolver(new DefaultDependencyResolver());
+ }
+
+ public static IDependencyResolver Current
+ {
+ get { return _instance.InnerCurrent; }
+ }
+
+ internal static IDependencyResolver CurrentCache
+ {
+ get { return _instance.InnerCurrentCache; }
+ }
+
+ public IDependencyResolver InnerCurrent
+ {
+ get { return _current; }
+ }
+
+ /// <summary>
+ /// Provides caching over results returned by Current.
+ /// </summary>
+ internal IDependencyResolver InnerCurrentCache
+ {
+ get { return _currentCache; }
+ }
+
+ public static void SetResolver(IDependencyResolver resolver)
+ {
+ _instance.InnerSetResolver(resolver);
+ }
+
+ public static void SetResolver(object commonServiceLocator)
+ {
+ _instance.InnerSetResolver(commonServiceLocator);
+ }
+
+ [SuppressMessage("Microsoft.Design", "CA1006:DoNotNestGenericTypesInMemberSignatures", Justification = "This is an appropriate nesting of generic types.")]
+ public static void SetResolver(Func<Type, object> getService, Func<Type, IEnumerable<object>> getServices)
+ {
+ _instance.InnerSetResolver(getService, getServices);
+ }
+
+ public void InnerSetResolver(IDependencyResolver resolver)
+ {
+ if (resolver == null)
+ {
+ throw new ArgumentNullException("resolver");
+ }
+
+ _current = resolver;
+ _currentCache = new CacheDependencyResolver(_current);
+ }
+
+ public void InnerSetResolver(object commonServiceLocator)
+ {
+ if (commonServiceLocator == null)
+ {
+ throw new ArgumentNullException("commonServiceLocator");
+ }
+
+ Type locatorType = commonServiceLocator.GetType();
+ MethodInfo getInstance = locatorType.GetMethod("GetInstance", new[] { typeof(Type) });
+ MethodInfo getInstances = locatorType.GetMethod("GetAllInstances", new[] { typeof(Type) });
+
+ if (getInstance == null ||
+ getInstance.ReturnType != typeof(object) ||
+ getInstances == null ||
+ getInstances.ReturnType != typeof(IEnumerable<object>))
+ {
+ throw new ArgumentException(
+ String.Format(
+ CultureInfo.CurrentCulture,
+ MvcResources.DependencyResolver_DoesNotImplementICommonServiceLocator,
+ locatorType.FullName),
+ "commonServiceLocator");
+ }
+
+ var getService = (Func<Type, object>)Delegate.CreateDelegate(typeof(Func<Type, object>), commonServiceLocator, getInstance);
+ var getServices = (Func<Type, IEnumerable<object>>)Delegate.CreateDelegate(typeof(Func<Type, IEnumerable<object>>), commonServiceLocator, getInstances);
+
+ InnerSetResolver(new DelegateBasedDependencyResolver(getService, getServices));
+ }
+
+ [SuppressMessage("Microsoft.Design", "CA1006:DoNotNestGenericTypesInMemberSignatures", Justification = "This is an appropriate nesting of generic types.")]
+ public void InnerSetResolver(Func<Type, object> getService, Func<Type, IEnumerable<object>> getServices)
+ {
+ if (getService == null)
+ {
+ throw new ArgumentNullException("getService");
+ }
+ if (getServices == null)
+ {
+ throw new ArgumentNullException("getServices");
+ }
+
+ InnerSetResolver(new DelegateBasedDependencyResolver(getService, getServices));
+ }
+
+ /// <summary>
+ /// Wraps an IDependencyResolver and ensures single instance per-type.
+ /// </summary>
+ /// <remarks>
+ /// Note it's possible for multiple threads to race and call the _resolver service multiple times.
+ /// We'll pick one winner and ignore the others and still guarantee a unique instance.
+ /// </remarks>
+ private sealed class CacheDependencyResolver : IDependencyResolver
+ {
+ private readonly ConcurrentDictionary<Type, object> _cache = new ConcurrentDictionary<Type, object>();
+ private readonly ConcurrentDictionary<Type, IEnumerable<object>> _cacheMultiple = new ConcurrentDictionary<Type, IEnumerable<object>>();
+
+ private readonly IDependencyResolver _resolver;
+
+ public CacheDependencyResolver(IDependencyResolver resolver)
+ {
+ _resolver = resolver;
+ }
+
+ public object GetService(Type serviceType)
+ {
+ return _cache.GetOrAdd(serviceType, _resolver.GetService);
+ }
+
+ public IEnumerable<object> GetServices(Type serviceType)
+ {
+ return _cacheMultiple.GetOrAdd(serviceType, _resolver.GetServices);
+ }
+ }
+
+ private class DefaultDependencyResolver : IDependencyResolver
+ {
+ [SuppressMessage("Microsoft.Design", "CA1031:DoNotCatchGeneralExceptionTypes", Justification = "This method might throw exceptions whose type we cannot strongly link against; namely, ActivationException from common service locator")]
+ public object GetService(Type serviceType)
+ {
+ // Since attempting to create an instance of an interface or an abstract type results in an exception, immediately return null
+ // to improve performance and the debugging experience with first-chance exceptions enabled.
+ if (serviceType.IsInterface || serviceType.IsAbstract)
+ {
+ return null;
+ }
+
+ try
+ {
+ return Activator.CreateInstance(serviceType);
+ }
+ catch
+ {
+ return null;
+ }
+ }
+
+ public IEnumerable<object> GetServices(Type serviceType)
+ {
+ return Enumerable.Empty<object>();
+ }
+ }
+
+ private class DelegateBasedDependencyResolver : IDependencyResolver
+ {
+ private Func<Type, object> _getService;
+ private Func<Type, IEnumerable<object>> _getServices;
+
+ public DelegateBasedDependencyResolver(Func<Type, object> getService, Func<Type, IEnumerable<object>> getServices)
+ {
+ _getService = getService;
+ _getServices = getServices;
+ }
+
+ [SuppressMessage("Microsoft.Design", "CA1031:DoNotCatchGeneralExceptionTypes", Justification = "This method might throw exceptions whose type we cannot strongly link against; namely, ActivationException from common service locator")]
+ public object GetService(Type type)
+ {
+ try
+ {
+ return _getService.Invoke(type);
+ }
+ catch
+ {
+ return null;
+ }
+ }
+
+ public IEnumerable<object> GetServices(Type type)
+ {
+ return _getServices(type);
+ }
+ }
+ }
+}
diff --git a/src/System.Web.Mvc/DependencyResolverExtensions.cs b/src/System.Web.Mvc/DependencyResolverExtensions.cs
new file mode 100644
index 00000000..db9c5129
--- /dev/null
+++ b/src/System.Web.Mvc/DependencyResolverExtensions.cs
@@ -0,0 +1,18 @@
+using System.Collections.Generic;
+using System.Linq;
+
+namespace System.Web.Mvc
+{
+ public static class DependencyResolverExtensions
+ {
+ public static TService GetService<TService>(this IDependencyResolver resolver)
+ {
+ return (TService)resolver.GetService(typeof(TService));
+ }
+
+ public static IEnumerable<TService> GetServices<TService>(this IDependencyResolver resolver)
+ {
+ return resolver.GetServices(typeof(TService)).Cast<TService>();
+ }
+ }
+}
diff --git a/src/System.Web.Mvc/DescriptorUtil.cs b/src/System.Web.Mvc/DescriptorUtil.cs
new file mode 100644
index 00000000..8e7905fb
--- /dev/null
+++ b/src/System.Web.Mvc/DescriptorUtil.cs
@@ -0,0 +1,86 @@
+using System.Collections.Generic;
+using System.Globalization;
+using System.Reflection;
+using System.Text;
+using System.Threading;
+
+namespace System.Web.Mvc
+{
+ internal static class DescriptorUtil
+ {
+ private static void AppendPartToUniqueIdBuilder(StringBuilder builder, object part)
+ {
+ if (part == null)
+ {
+ builder.Append("[-1]");
+ }
+ else
+ {
+ string partString = Convert.ToString(part, CultureInfo.InvariantCulture);
+ builder.AppendFormat("[{0}]{1}", partString.Length, partString);
+ }
+ }
+
+ public static string CreateUniqueId(params object[] parts)
+ {
+ return CreateUniqueId((IEnumerable<object>)parts);
+ }
+
+ public static string CreateUniqueId(IEnumerable<object> parts)
+ {
+ // returns a unique string made up of the pieces passed in
+ StringBuilder builder = new StringBuilder();
+ foreach (object part in parts)
+ {
+ // We can special-case certain part types
+
+ MemberInfo memberInfo = part as MemberInfo;
+ if (memberInfo != null)
+ {
+ AppendPartToUniqueIdBuilder(builder, memberInfo.Module.ModuleVersionId);
+ AppendPartToUniqueIdBuilder(builder, memberInfo.MetadataToken);
+ continue;
+ }
+
+ IUniquelyIdentifiable uniquelyIdentifiable = part as IUniquelyIdentifiable;
+ if (uniquelyIdentifiable != null)
+ {
+ AppendPartToUniqueIdBuilder(builder, uniquelyIdentifiable.UniqueId);
+ continue;
+ }
+
+ AppendPartToUniqueIdBuilder(builder, part);
+ }
+
+ return builder.ToString();
+ }
+
+ public static TDescriptor[] LazilyFetchOrCreateDescriptors<TReflection, TDescriptor>(ref TDescriptor[] cacheLocation, Func<TReflection[]> initializer, Func<TReflection, TDescriptor> converter)
+ {
+ // did we already calculate this once?
+ TDescriptor[] existingCache = Interlocked.CompareExchange(ref cacheLocation, null, null);
+ if (existingCache != null)
+ {
+ return existingCache;
+ }
+
+ // Note: since this code operates on arrays it is more efficient to call simple array operations
+ // instead of LINQ-y extension methods such as Select and Where. DO NOT attempt to simplify this
+ // without testing the performance impact.
+ TReflection[] memberInfos = initializer();
+ List<TDescriptor> descriptorsList = new List<TDescriptor>(memberInfos.Length);
+ for (int i = 0; i < memberInfos.Length; i++)
+ {
+ TDescriptor descriptor = converter(memberInfos[i]);
+ if (descriptor != null)
+ {
+ descriptorsList.Add(descriptor);
+ }
+ }
+ TDescriptor[] descriptors = descriptorsList.ToArray();
+
+ TDescriptor[] updatedCache = Interlocked.CompareExchange(ref cacheLocation, descriptors, null);
+ return updatedCache ?? descriptors;
+ }
+ }
+}
diff --git a/src/System.Web.Mvc/DictionaryHelpers.cs b/src/System.Web.Mvc/DictionaryHelpers.cs
new file mode 100644
index 00000000..7f917971
--- /dev/null
+++ b/src/System.Web.Mvc/DictionaryHelpers.cs
@@ -0,0 +1,56 @@
+using System.Collections.Generic;
+using System.Linq;
+
+namespace System.Web.Mvc
+{
+ internal static class DictionaryHelpers
+ {
+ public static IEnumerable<KeyValuePair<string, TValue>> FindKeysWithPrefix<TValue>(IDictionary<string, TValue> dictionary, string prefix)
+ {
+ TValue exactMatchValue;
+ if (dictionary.TryGetValue(prefix, out exactMatchValue))
+ {
+ yield return new KeyValuePair<string, TValue>(prefix, exactMatchValue);
+ }
+
+ foreach (var entry in dictionary)
+ {
+ string key = entry.Key;
+
+ if (key.Length <= prefix.Length)
+ {
+ continue;
+ }
+
+ if (!key.StartsWith(prefix, StringComparison.OrdinalIgnoreCase))
+ {
+ continue;
+ }
+
+ char charAfterPrefix = key[prefix.Length];
+ switch (charAfterPrefix)
+ {
+ case '[':
+ case '.':
+ yield return entry;
+ break;
+ }
+ }
+ }
+
+ public static bool DoesAnyKeyHavePrefix<TValue>(IDictionary<string, TValue> dictionary, string prefix)
+ {
+ return FindKeysWithPrefix(dictionary, prefix).Any();
+ }
+
+ public static TValue GetOrDefault<TKey, TValue>(this IDictionary<TKey, TValue> dict, TKey key, TValue @default)
+ {
+ TValue value;
+ if (dict.TryGetValue(key, out value))
+ {
+ return value;
+ }
+ return @default;
+ }
+ }
+}
diff --git a/src/System.Web.Mvc/DictionaryValueProvider`1.cs b/src/System.Web.Mvc/DictionaryValueProvider`1.cs
new file mode 100644
index 00000000..d1abfa94
--- /dev/null
+++ b/src/System.Web.Mvc/DictionaryValueProvider`1.cs
@@ -0,0 +1,65 @@
+using System.Collections.Generic;
+using System.Globalization;
+
+namespace System.Web.Mvc
+{
+ public class DictionaryValueProvider<TValue> : IValueProvider, IEnumerableValueProvider
+ {
+ private readonly HashSet<string> _prefixes = new HashSet<string>(StringComparer.OrdinalIgnoreCase);
+ private readonly Dictionary<string, ValueProviderResult> _values = new Dictionary<string, ValueProviderResult>(StringComparer.OrdinalIgnoreCase);
+
+ public DictionaryValueProvider(IDictionary<string, TValue> dictionary, CultureInfo culture)
+ {
+ if (dictionary == null)
+ {
+ throw new ArgumentNullException("dictionary");
+ }
+
+ AddValues(dictionary, culture);
+ }
+
+ private void AddValues(IDictionary<string, TValue> dictionary, CultureInfo culture)
+ {
+ if (dictionary.Count > 0)
+ {
+ _prefixes.Add(String.Empty);
+ }
+
+ foreach (var entry in dictionary)
+ {
+ _prefixes.UnionWith(ValueProviderUtil.GetPrefixes(entry.Key));
+
+ object rawValue = entry.Value;
+ string attemptedValue = Convert.ToString(rawValue, culture);
+ _values[entry.Key] = new ValueProviderResult(rawValue, attemptedValue, culture);
+ }
+ }
+
+ public virtual bool ContainsPrefix(string prefix)
+ {
+ if (prefix == null)
+ {
+ throw new ArgumentNullException("prefix");
+ }
+
+ return _prefixes.Contains(prefix);
+ }
+
+ public virtual ValueProviderResult GetValue(string key)
+ {
+ if (key == null)
+ {
+ throw new ArgumentNullException("key");
+ }
+
+ ValueProviderResult valueProviderResult;
+ _values.TryGetValue(key, out valueProviderResult);
+ return valueProviderResult;
+ }
+
+ public virtual IDictionary<string, string> GetKeysFromPrefix(string prefix)
+ {
+ return ValueProviderUtil.GetKeysFromPrefix(_prefixes, prefix);
+ }
+ }
+}
diff --git a/src/System.Web.Mvc/DynamicViewDataDictionary.cs b/src/System.Web.Mvc/DynamicViewDataDictionary.cs
new file mode 100644
index 00000000..b7b36b75
--- /dev/null
+++ b/src/System.Web.Mvc/DynamicViewDataDictionary.cs
@@ -0,0 +1,47 @@
+using System.Collections.Generic;
+using System.Diagnostics;
+using System.Dynamic;
+
+namespace System.Web.Mvc
+{
+ internal sealed class DynamicViewDataDictionary : DynamicObject
+ {
+ private readonly Func<ViewDataDictionary> _viewDataThunk;
+
+ public DynamicViewDataDictionary(Func<ViewDataDictionary> viewDataThunk)
+ {
+ _viewDataThunk = viewDataThunk;
+ }
+
+ private ViewDataDictionary ViewData
+ {
+ get
+ {
+ ViewDataDictionary viewData = _viewDataThunk();
+ Debug.Assert(viewData != null);
+ return viewData;
+ }
+ }
+
+ // Implementing this function improves the debugging experience as it provides the debugger with the list of all
+ // the properties currently defined on the object
+ public override IEnumerable<string> GetDynamicMemberNames()
+ {
+ return ViewData.Keys;
+ }
+
+ public override bool TryGetMember(GetMemberBinder binder, out object result)
+ {
+ result = ViewData[binder.Name];
+ // since ViewDataDictionary always returns a result even if the key does not exist, always return true
+ return true;
+ }
+
+ public override bool TrySetMember(SetMemberBinder binder, object value)
+ {
+ ViewData[binder.Name] = value;
+ // you can always set a key in the dictionary so return true
+ return true;
+ }
+ }
+}
diff --git a/src/System.Web.Mvc/EmptyModelMetadataProvider.cs b/src/System.Web.Mvc/EmptyModelMetadataProvider.cs
new file mode 100644
index 00000000..000f334c
--- /dev/null
+++ b/src/System.Web.Mvc/EmptyModelMetadataProvider.cs
@@ -0,0 +1,12 @@
+using System.Collections.Generic;
+
+namespace System.Web.Mvc
+{
+ public class EmptyModelMetadataProvider : AssociatedMetadataProvider
+ {
+ protected override ModelMetadata CreateMetadata(IEnumerable<Attribute> attributes, Type containerType, Func<object> modelAccessor, Type modelType, string propertyName)
+ {
+ return new ModelMetadata(this, containerType, modelAccessor, modelType, propertyName);
+ }
+ }
+}
diff --git a/src/System.Web.Mvc/EmptyModelValidatorProvider.cs b/src/System.Web.Mvc/EmptyModelValidatorProvider.cs
new file mode 100644
index 00000000..0bd617c4
--- /dev/null
+++ b/src/System.Web.Mvc/EmptyModelValidatorProvider.cs
@@ -0,0 +1,13 @@
+using System.Collections.Generic;
+using System.Linq;
+
+namespace System.Web.Mvc
+{
+ public class EmptyModelValidatorProvider : ModelValidatorProvider
+ {
+ public override IEnumerable<ModelValidator> GetValidators(ModelMetadata metadata, ControllerContext context)
+ {
+ return Enumerable.Empty<ModelValidator>();
+ }
+ }
+}
diff --git a/src/System.Web.Mvc/EmptyResult.cs b/src/System.Web.Mvc/EmptyResult.cs
new file mode 100644
index 00000000..f67ab681
--- /dev/null
+++ b/src/System.Web.Mvc/EmptyResult.cs
@@ -0,0 +1,17 @@
+namespace System.Web.Mvc
+{
+ // represents a result that doesn't do anything, like a controller action returning null
+ public class EmptyResult : ActionResult
+ {
+ private static readonly EmptyResult _singleton = new EmptyResult();
+
+ internal static EmptyResult Instance
+ {
+ get { return _singleton; }
+ }
+
+ public override void ExecuteResult(ControllerContext context)
+ {
+ }
+ }
+}
diff --git a/src/System.Web.Mvc/Error.cs b/src/System.Web.Mvc/Error.cs
new file mode 100644
index 00000000..173de8ca
--- /dev/null
+++ b/src/System.Web.Mvc/Error.cs
@@ -0,0 +1,76 @@
+using System.Globalization;
+using System.Web.Mvc.Async;
+using System.Web.Mvc.Properties;
+
+namespace System.Web.Mvc
+{
+ internal static class Error
+ {
+ public static InvalidOperationException AsyncActionMethodSelector_CouldNotFindMethod(string methodName, Type controllerType)
+ {
+ string message = String.Format(CultureInfo.CurrentCulture, MvcResources.AsyncActionMethodSelector_CouldNotFindMethod,
+ methodName, controllerType);
+ return new InvalidOperationException(message);
+ }
+
+ public static InvalidOperationException AsyncCommon_AsyncResultAlreadyConsumed()
+ {
+ return new InvalidOperationException(MvcResources.AsyncCommon_AsyncResultAlreadyConsumed);
+ }
+
+ public static InvalidOperationException AsyncCommon_ControllerMustImplementIAsyncManagerContainer(Type actualControllerType)
+ {
+ string message = String.Format(CultureInfo.CurrentCulture, MvcResources.AsyncCommon_ControllerMustImplementIAsyncManagerContainer,
+ actualControllerType);
+ return new InvalidOperationException(message);
+ }
+
+ public static ArgumentException AsyncCommon_InvalidAsyncResult(string parameterName)
+ {
+ return new ArgumentException(MvcResources.AsyncCommon_InvalidAsyncResult, parameterName);
+ }
+
+ public static ArgumentOutOfRangeException AsyncCommon_InvalidTimeout(string parameterName)
+ {
+ return new ArgumentOutOfRangeException(parameterName, MvcResources.AsyncCommon_InvalidTimeout);
+ }
+
+ public static InvalidOperationException ChildActionOnlyAttribute_MustBeInChildRequest(ActionDescriptor actionDescriptor)
+ {
+ string message = String.Format(CultureInfo.CurrentCulture, MvcResources.ChildActionOnlyAttribute_MustBeInChildRequest,
+ actionDescriptor.ActionName);
+ return new InvalidOperationException(message);
+ }
+
+ public static ArgumentException ParameterCannotBeNullOrEmpty(string parameterName)
+ {
+ return new ArgumentException(MvcResources.Common_NullOrEmpty, parameterName);
+ }
+
+ public static InvalidOperationException PropertyCannotBeNullOrEmpty(string propertyName)
+ {
+ string message = String.Format(CultureInfo.CurrentCulture, MvcResources.Common_PropertyCannotBeNullOrEmpty,
+ propertyName);
+ return new InvalidOperationException(message);
+ }
+
+ public static SynchronousOperationException SynchronizationContextUtil_ExceptionThrown(Exception innerException)
+ {
+ return new SynchronousOperationException(MvcResources.SynchronizationContextUtil_ExceptionThrown, innerException);
+ }
+
+ public static InvalidOperationException ViewDataDictionary_WrongTModelType(Type valueType, Type modelType)
+ {
+ string message = String.Format(CultureInfo.CurrentCulture, MvcResources.ViewDataDictionary_WrongTModelType,
+ valueType, modelType);
+ return new InvalidOperationException(message);
+ }
+
+ public static InvalidOperationException ViewDataDictionary_ModelCannotBeNull(Type modelType)
+ {
+ string message = String.Format(CultureInfo.CurrentCulture, MvcResources.ViewDataDictionary_ModelCannotBeNull,
+ modelType);
+ return new InvalidOperationException(message);
+ }
+ }
+}
diff --git a/src/System.Web.Mvc/ExceptionContext.cs b/src/System.Web.Mvc/ExceptionContext.cs
new file mode 100644
index 00000000..29cbf6df
--- /dev/null
+++ b/src/System.Web.Mvc/ExceptionContext.cs
@@ -0,0 +1,36 @@
+using System.Diagnostics.CodeAnalysis;
+
+namespace System.Web.Mvc
+{
+ public class ExceptionContext : ControllerContext
+ {
+ private ActionResult _result;
+
+ // parameterless constructor used for mocking
+ public ExceptionContext()
+ {
+ }
+
+ [SuppressMessage("Microsoft.Usage", "CA2214:DoNotCallOverridableMethodsInConstructors", Justification = "The virtual property setters are only to support mocking frameworks, in which case this constructor shouldn't be called anyway.")]
+ public ExceptionContext(ControllerContext controllerContext, Exception exception)
+ : base(controllerContext)
+ {
+ if (exception == null)
+ {
+ throw new ArgumentNullException("exception");
+ }
+
+ Exception = exception;
+ }
+
+ public virtual Exception Exception { get; set; }
+
+ public bool ExceptionHandled { get; set; }
+
+ public ActionResult Result
+ {
+ get { return _result ?? EmptyResult.Instance; }
+ set { _result = value; }
+ }
+ }
+}
diff --git a/src/System.Web.Mvc/ExpressionHelper.cs b/src/System.Web.Mvc/ExpressionHelper.cs
new file mode 100644
index 00000000..69432305
--- /dev/null
+++ b/src/System.Web.Mvc/ExpressionHelper.cs
@@ -0,0 +1,131 @@
+using System.Collections.Generic;
+using System.Globalization;
+using System.Linq;
+using System.Linq.Expressions;
+using System.Reflection;
+using System.Web.Mvc.ExpressionUtil;
+using System.Web.Mvc.Properties;
+
+namespace System.Web.Mvc
+{
+ public static class ExpressionHelper
+ {
+ public static string GetExpressionText(string expression)
+ {
+ return
+ String.Equals(expression, "model", StringComparison.OrdinalIgnoreCase)
+ ? String.Empty // If it's exactly "model", then give them an empty string, to replicate the lambda behavior
+ : expression;
+ }
+
+ public static string GetExpressionText(LambdaExpression expression)
+ {
+ // Split apart the expression string for property/field accessors to create its name
+ Stack<string> nameParts = new Stack<string>();
+ Expression part = expression.Body;
+
+ while (part != null)
+ {
+ if (part.NodeType == ExpressionType.Call)
+ {
+ MethodCallExpression methodExpression = (MethodCallExpression)part;
+
+ if (!IsSingleArgumentIndexer(methodExpression))
+ {
+ break;
+ }
+
+ nameParts.Push(
+ GetIndexerInvocation(
+ methodExpression.Arguments.Single(),
+ expression.Parameters.ToArray()));
+
+ part = methodExpression.Object;
+ }
+ else if (part.NodeType == ExpressionType.ArrayIndex)
+ {
+ BinaryExpression binaryExpression = (BinaryExpression)part;
+
+ nameParts.Push(
+ GetIndexerInvocation(
+ binaryExpression.Right,
+ expression.Parameters.ToArray()));
+
+ part = binaryExpression.Left;
+ }
+ else if (part.NodeType == ExpressionType.MemberAccess)
+ {
+ MemberExpression memberExpressionPart = (MemberExpression)part;
+ nameParts.Push("." + memberExpressionPart.Member.Name);
+ part = memberExpressionPart.Expression;
+ }
+ else if (part.NodeType == ExpressionType.Parameter)
+ {
+ // Dev10 Bug #907611
+ // When the expression is parameter based (m => m.Something...), we'll push an empty
+ // string onto the stack and stop evaluating. The extra empty string makes sure that
+ // we don't accidentally cut off too much of m => m.Model.
+ nameParts.Push(String.Empty);
+ part = null;
+ }
+ else
+ {
+ break;
+ }
+ }
+
+ // If it starts with "model", then strip that away
+ if (nameParts.Count > 0 && String.Equals(nameParts.Peek(), ".model", StringComparison.OrdinalIgnoreCase))
+ {
+ nameParts.Pop();
+ }
+
+ if (nameParts.Count > 0)
+ {
+ return nameParts.Aggregate((left, right) => left + right).TrimStart('.');
+ }
+
+ return String.Empty;
+ }
+
+ private static string GetIndexerInvocation(Expression expression, ParameterExpression[] parameters)
+ {
+ Expression converted = Expression.Convert(expression, typeof(object));
+ ParameterExpression fakeParameter = Expression.Parameter(typeof(object), null);
+ Expression<Func<object, object>> lambda = Expression.Lambda<Func<object, object>>(converted, fakeParameter);
+ Func<object, object> func;
+
+ try
+ {
+ func = CachedExpressionCompiler.Process(lambda);
+ }
+ catch (InvalidOperationException ex)
+ {
+ throw new InvalidOperationException(
+ String.Format(
+ CultureInfo.CurrentCulture,
+ MvcResources.ExpressionHelper_InvalidIndexerExpression,
+ expression,
+ parameters[0].Name),
+ ex);
+ }
+
+ return "[" + Convert.ToString(func(null), CultureInfo.InvariantCulture) + "]";
+ }
+
+ internal static bool IsSingleArgumentIndexer(Expression expression)
+ {
+ MethodCallExpression methodExpression = expression as MethodCallExpression;
+ if (methodExpression == null || methodExpression.Arguments.Count != 1)
+ {
+ return false;
+ }
+
+ return methodExpression.Method
+ .DeclaringType
+ .GetDefaultMembers()
+ .OfType<PropertyInfo>()
+ .Any(p => p.GetGetMethod() == methodExpression.Method);
+ }
+ }
+}
diff --git a/src/System.Web.Mvc/ExpressionUtil/BinaryExpressionFingerprint.cs b/src/System.Web.Mvc/ExpressionUtil/BinaryExpressionFingerprint.cs
new file mode 100644
index 00000000..e1c50e74
--- /dev/null
+++ b/src/System.Web.Mvc/ExpressionUtil/BinaryExpressionFingerprint.cs
@@ -0,0 +1,41 @@
+using System.Diagnostics.CodeAnalysis;
+using System.Linq.Expressions;
+using System.Reflection;
+
+#pragma warning disable 659 // overrides AddToHashCodeCombiner instead
+
+namespace System.Web.Mvc.ExpressionUtil
+{
+ // BinaryExpression fingerprint class
+ // Useful for things like array[index]
+
+ [SuppressMessage("Microsoft.Usage", "CA2218:OverrideGetHashCodeOnOverridingEquals", Justification = "Overrides AddToHashCodeCombiner() instead.")]
+ internal sealed class BinaryExpressionFingerprint : ExpressionFingerprint
+ {
+ public BinaryExpressionFingerprint(ExpressionType nodeType, Type type, MethodInfo method)
+ : base(nodeType, type)
+ {
+ // Other properties on BinaryExpression (like IsLifted / IsLiftedToNull) are simply derived
+ // from Type and NodeType, so they're not necessary for inclusion in the fingerprint.
+
+ Method = method;
+ }
+
+ // http://msdn.microsoft.com/en-us/library/system.linq.expressions.binaryexpression.method.aspx
+ public MethodInfo Method { get; private set; }
+
+ public override bool Equals(object obj)
+ {
+ BinaryExpressionFingerprint other = obj as BinaryExpressionFingerprint;
+ return (other != null)
+ && Equals(this.Method, other.Method)
+ && this.Equals(other);
+ }
+
+ internal override void AddToHashCodeCombiner(HashCodeCombiner combiner)
+ {
+ combiner.AddObject(Method);
+ base.AddToHashCodeCombiner(combiner);
+ }
+ }
+}
diff --git a/src/System.Web.Mvc/ExpressionUtil/CachedExpressionCompiler.cs b/src/System.Web.Mvc/ExpressionUtil/CachedExpressionCompiler.cs
new file mode 100644
index 00000000..9d6d3fba
--- /dev/null
+++ b/src/System.Web.Mvc/ExpressionUtil/CachedExpressionCompiler.cs
@@ -0,0 +1,142 @@
+using System.Collections.Concurrent;
+using System.Collections.Generic;
+using System.Linq.Expressions;
+using System.Reflection;
+
+namespace System.Web.Mvc.ExpressionUtil
+{
+ internal static class CachedExpressionCompiler
+ {
+ // This is the entry point to the cached expression compilation system. The system
+ // will try to turn the expression into an actual delegate as quickly as possible,
+ // relying on cache lookups and other techniques to save time if appropriate.
+ // If the provided expression is particularly obscure and the system doesn't know
+ // how to handle it, we'll just compile the expression as normal.
+ public static Func<TModel, TValue> Process<TModel, TValue>(Expression<Func<TModel, TValue>> lambdaExpression)
+ {
+ return Compiler<TModel, TValue>.Compile(lambdaExpression);
+ }
+
+ private static class Compiler<TIn, TOut>
+ {
+ private static Func<TIn, TOut> _identityFunc;
+
+ private static readonly ConcurrentDictionary<MemberInfo, Func<TIn, TOut>> _simpleMemberAccessDict =
+ new ConcurrentDictionary<MemberInfo, Func<TIn, TOut>>();
+
+ private static readonly ConcurrentDictionary<MemberInfo, Func<object, TOut>> _constMemberAccessDict =
+ new ConcurrentDictionary<MemberInfo, Func<object, TOut>>();
+
+ private static readonly ConcurrentDictionary<ExpressionFingerprintChain, Hoisted<TIn, TOut>> _fingerprintedCache =
+ new ConcurrentDictionary<ExpressionFingerprintChain, Hoisted<TIn, TOut>>();
+
+ public static Func<TIn, TOut> Compile(Expression<Func<TIn, TOut>> expr)
+ {
+ return CompileFromIdentityFunc(expr)
+ ?? CompileFromConstLookup(expr)
+ ?? CompileFromMemberAccess(expr)
+ ?? CompileFromFingerprint(expr)
+ ?? CompileSlow(expr);
+ }
+
+ private static Func<TIn, TOut> CompileFromConstLookup(Expression<Func<TIn, TOut>> expr)
+ {
+ ConstantExpression constExpr = expr.Body as ConstantExpression;
+ if (constExpr != null)
+ {
+ // model => {const}
+
+ TOut constantValue = (TOut)constExpr.Value;
+ return _ => constantValue;
+ }
+
+ return null;
+ }
+
+ private static Func<TIn, TOut> CompileFromIdentityFunc(Expression<Func<TIn, TOut>> expr)
+ {
+ if (expr.Body == expr.Parameters[0])
+ {
+ // model => model
+
+ // don't need to lock, as all identity funcs are identical
+ if (_identityFunc == null)
+ {
+ _identityFunc = expr.Compile();
+ }
+
+ return _identityFunc;
+ }
+
+ return null;
+ }
+
+ private static Func<TIn, TOut> CompileFromFingerprint(Expression<Func<TIn, TOut>> expr)
+ {
+ List<object> capturedConstants;
+ ExpressionFingerprintChain fingerprint = FingerprintingExpressionVisitor.GetFingerprintChain(expr, out capturedConstants);
+
+ if (fingerprint != null)
+ {
+ var del = _fingerprintedCache.GetOrAdd(fingerprint, _ =>
+ {
+ // Fingerprinting succeeded, but there was a cache miss. Rewrite the expression
+ // and add the rewritten expression to the cache.
+
+ var hoistedExpr = HoistingExpressionVisitor<TIn, TOut>.Hoist(expr);
+ return hoistedExpr.Compile();
+ });
+ return model => del(model, capturedConstants);
+ }
+
+ // couldn't be fingerprinted
+ return null;
+ }
+
+ private static Func<TIn, TOut> CompileFromMemberAccess(Expression<Func<TIn, TOut>> expr)
+ {
+ // Performance tests show that on the x64 platform, special-casing static member and
+ // captured local variable accesses is faster than letting the fingerprinting system
+ // handle them. On the x86 platform, the fingerprinting system is faster, but only
+ // by around one microsecond, so it's not worth it to complicate the logic here with
+ // an architecture check.
+
+ MemberExpression memberExpr = expr.Body as MemberExpression;
+ if (memberExpr != null)
+ {
+ if (memberExpr.Expression == expr.Parameters[0] || memberExpr.Expression == null)
+ {
+ // model => model.Member or model => StaticMember
+ return _simpleMemberAccessDict.GetOrAdd(memberExpr.Member, _ => expr.Compile());
+ }
+
+ ConstantExpression constExpr = memberExpr.Expression as ConstantExpression;
+ if (constExpr != null)
+ {
+ // model => {const}.Member (captured local variable)
+ var del = _constMemberAccessDict.GetOrAdd(memberExpr.Member, _ =>
+ {
+ // rewrite as capturedLocal => ((TDeclaringType)capturedLocal).Member
+ var constParamExpr = Expression.Parameter(typeof(object), "capturedLocal");
+ var constCastExpr = Expression.Convert(constParamExpr, memberExpr.Member.DeclaringType);
+ var newMemberAccessExpr = memberExpr.Update(constCastExpr);
+ var newLambdaExpr = Expression.Lambda<Func<object, TOut>>(newMemberAccessExpr, constParamExpr);
+ return newLambdaExpr.Compile();
+ });
+
+ object capturedLocal = constExpr.Value;
+ return _ => del(capturedLocal);
+ }
+ }
+
+ return null;
+ }
+
+ private static Func<TIn, TOut> CompileSlow(Expression<Func<TIn, TOut>> expr)
+ {
+ // fallback compilation system - just compile the expression directly
+ return expr.Compile();
+ }
+ }
+ }
+}
diff --git a/src/System.Web.Mvc/ExpressionUtil/ConditionalExpressionFingerprint.cs b/src/System.Web.Mvc/ExpressionUtil/ConditionalExpressionFingerprint.cs
new file mode 100644
index 00000000..ceb7d327
--- /dev/null
+++ b/src/System.Web.Mvc/ExpressionUtil/ConditionalExpressionFingerprint.cs
@@ -0,0 +1,28 @@
+using System.Diagnostics.CodeAnalysis;
+using System.Linq.Expressions;
+
+#pragma warning disable 659 // overrides AddToHashCodeCombiner instead
+
+namespace System.Web.Mvc.ExpressionUtil
+{
+ // ConditionalExpression fingerprint class
+ // Expression of form (test) ? ifTrue : ifFalse
+
+ [SuppressMessage("Microsoft.Usage", "CA2218:OverrideGetHashCodeOnOverridingEquals", Justification = "Overrides AddToHashCodeCombiner() instead.")]
+ internal sealed class ConditionalExpressionFingerprint : ExpressionFingerprint
+ {
+ public ConditionalExpressionFingerprint(ExpressionType nodeType, Type type)
+ : base(nodeType, type)
+ {
+ // There are no properties on ConditionalExpression that are worth including in
+ // the fingerprint.
+ }
+
+ public override bool Equals(object obj)
+ {
+ ConditionalExpressionFingerprint other = obj as ConditionalExpressionFingerprint;
+ return (other != null)
+ && this.Equals(other);
+ }
+ }
+}
diff --git a/src/System.Web.Mvc/ExpressionUtil/ConstantExpressionFingerprint.cs b/src/System.Web.Mvc/ExpressionUtil/ConstantExpressionFingerprint.cs
new file mode 100644
index 00000000..484e7976
--- /dev/null
+++ b/src/System.Web.Mvc/ExpressionUtil/ConstantExpressionFingerprint.cs
@@ -0,0 +1,32 @@
+using System.Diagnostics.CodeAnalysis;
+using System.Linq.Expressions;
+
+#pragma warning disable 659 // overrides AddToHashCodeCombiner instead
+
+namespace System.Web.Mvc.ExpressionUtil
+{
+ // ConstantExpression fingerprint class
+ //
+ // A ConstantExpression might represent a captured local variable, so we can't compile
+ // the value directly into the cached function. Instead, a placeholder is generated
+ // and the value is hoisted into a local variables array. This placeholder can then
+ // be compiled and cached, and the array lookup happens at runtime.
+
+ [SuppressMessage("Microsoft.Usage", "CA2218:OverrideGetHashCodeOnOverridingEquals", Justification = "Overrides AddToHashCodeCombiner() instead.")]
+ internal sealed class ConstantExpressionFingerprint : ExpressionFingerprint
+ {
+ public ConstantExpressionFingerprint(ExpressionType nodeType, Type type)
+ : base(nodeType, type)
+ {
+ // There are no properties on ConstantExpression that are worth including in
+ // the fingerprint.
+ }
+
+ public override bool Equals(object obj)
+ {
+ ConstantExpressionFingerprint other = obj as ConstantExpressionFingerprint;
+ return (other != null)
+ && this.Equals(other);
+ }
+ }
+}
diff --git a/src/System.Web.Mvc/ExpressionUtil/DefaultExpressionFingerprint.cs b/src/System.Web.Mvc/ExpressionUtil/DefaultExpressionFingerprint.cs
new file mode 100644
index 00000000..e8503774
--- /dev/null
+++ b/src/System.Web.Mvc/ExpressionUtil/DefaultExpressionFingerprint.cs
@@ -0,0 +1,28 @@
+using System.Diagnostics.CodeAnalysis;
+using System.Linq.Expressions;
+
+#pragma warning disable 659 // overrides AddToHashCodeCombiner instead
+
+namespace System.Web.Mvc.ExpressionUtil
+{
+ // DefaultExpression fingerprint class
+ // Expression of form default(T)
+
+ [SuppressMessage("Microsoft.Usage", "CA2218:OverrideGetHashCodeOnOverridingEquals", Justification = "Overrides AddToHashCodeCombiner() instead.")]
+ internal sealed class DefaultExpressionFingerprint : ExpressionFingerprint
+ {
+ public DefaultExpressionFingerprint(ExpressionType nodeType, Type type)
+ : base(nodeType, type)
+ {
+ // There are no properties on DefaultExpression that are worth including in
+ // the fingerprint.
+ }
+
+ public override bool Equals(object obj)
+ {
+ DefaultExpressionFingerprint other = obj as DefaultExpressionFingerprint;
+ return (other != null)
+ && this.Equals(other);
+ }
+ }
+}
diff --git a/src/System.Web.Mvc/ExpressionUtil/ExpressionFingerprint.cs b/src/System.Web.Mvc/ExpressionUtil/ExpressionFingerprint.cs
new file mode 100644
index 00000000..1d9cf5e2
--- /dev/null
+++ b/src/System.Web.Mvc/ExpressionUtil/ExpressionFingerprint.cs
@@ -0,0 +1,47 @@
+using System.Linq.Expressions;
+
+namespace System.Web.Mvc.ExpressionUtil
+{
+ // Serves as the base class for all expression fingerprints. Provides a default implementation
+ // of GetHashCode().
+
+ internal abstract class ExpressionFingerprint
+ {
+ protected ExpressionFingerprint(ExpressionType nodeType, Type type)
+ {
+ NodeType = nodeType;
+ Type = type;
+ }
+
+ // the type of expression node, e.g. OP_ADD, MEMBER_ACCESS, etc.
+ public ExpressionType NodeType { get; private set; }
+
+ // the CLR type resulting from this expression, e.g. int, string, etc.
+ public Type Type { get; private set; }
+
+ internal virtual void AddToHashCodeCombiner(HashCodeCombiner combiner)
+ {
+ combiner.AddInt32((int)NodeType);
+ combiner.AddObject(Type);
+ }
+
+ protected bool Equals(ExpressionFingerprint other)
+ {
+ return (other != null)
+ && (this.NodeType == other.NodeType)
+ && Equals(this.Type, other.Type);
+ }
+
+ public override bool Equals(object obj)
+ {
+ return Equals(obj as ExpressionFingerprint);
+ }
+
+ public override int GetHashCode()
+ {
+ HashCodeCombiner combiner = new HashCodeCombiner();
+ AddToHashCodeCombiner(combiner);
+ return combiner.CombinedHash;
+ }
+ }
+}
diff --git a/src/System.Web.Mvc/ExpressionUtil/ExpressionFingerprintChain.cs b/src/System.Web.Mvc/ExpressionUtil/ExpressionFingerprintChain.cs
new file mode 100644
index 00000000..3399dcee
--- /dev/null
+++ b/src/System.Web.Mvc/ExpressionUtil/ExpressionFingerprintChain.cs
@@ -0,0 +1,85 @@
+using System.Collections.Generic;
+
+namespace System.Web.Mvc.ExpressionUtil
+{
+ // Expression fingerprint chain class
+ // Contains information used for generalizing, comparing, and recreating Expression instances
+ //
+ // Since Expression objects are immutable and are recreated for every invocation of an expression
+ // helper method, they can't be compared directly. Fingerprinting Expression objects allows
+ // information about them to be abstracted away, and the fingerprints can be directly compared.
+ // Consider the process of fingerprinting that all values (parameters, constants, etc.) are hoisted
+ // and replaced with dummies. What remains can be decomposed into a sequence of operations on specific
+ // types and specific inputs.
+ //
+ // Some sample fingerprints chains:
+ //
+ // 2 + 4 -> OP_ADD, CONST:int, NULL, CONST:int
+ // 2 + 8 -> OP_ADD, CONST:int, NULL, CONST:int
+ // 2.0 + 4.0 -> OP_ADD, CONST:double, NULL, CONST:double
+ //
+ // 2 + 4 and 2 + 8 have the same fingerprint, but 2.0 + 4.0 has a different fingerprint since its
+ // underlying types differ. Note that this looks a bit like prefix notation and is a side effect
+ // of how the ExpressionVisitor class recurses into expressions. (Occasionally there will be a NULL
+ // in the fingerprint chain, which depending on context can denote a static member, a null Conversion
+ // in a BinaryExpression, and so forth.)
+ //
+ // "Hello " + "world" -> OP_ADD, CONST:string, NULL, CONST:string
+ // "Hello " + {model} -> OP_ADD, CONST:string, NULL, PARAM_0:string
+ //
+ // These string concatenations have different fingerprints since the inputs are provided differently:
+ // one is a constant, the other is a parameter.
+ //
+ // ({model} ?? "sample").Length -> MEMBER_ACCESS(String.Length), OP_COALESCE, PARAM_0:string, NULL, CONST:string
+ // ({model} ?? "other sample").Length -> MEMBER_ACCESS(String.Length), OP_COALESCE, PARAM_0:string, NULL, CONST:string
+ //
+ // These expressions have the same fingerprint since all constants of the same underlying type are
+ // treated equally.
+ //
+ // It's also important that the fingerprints don't reference the actual Expression objects that were
+ // used to generate them, as the fingerprints will be cached, and caching a fingerprint that references
+ // an Expression will root the Expression (and any objects it references).
+
+ internal sealed class ExpressionFingerprintChain : IEquatable<ExpressionFingerprintChain>
+ {
+ public readonly List<ExpressionFingerprint> Elements = new List<ExpressionFingerprint>();
+
+ public bool Equals(ExpressionFingerprintChain other)
+ {
+ // Two chains are considered equal if two elements appearing in the same index in
+ // each chain are equal (value equality, not referential equality).
+
+ if (other == null)
+ {
+ return false;
+ }
+
+ if (this.Elements.Count != other.Elements.Count)
+ {
+ return false;
+ }
+
+ for (int i = 0; i < this.Elements.Count; i++)
+ {
+ if (!Equals(this.Elements[i], other.Elements[i]))
+ {
+ return false;
+ }
+ }
+
+ return true;
+ }
+
+ public override bool Equals(object obj)
+ {
+ return Equals(obj as ExpressionFingerprintChain);
+ }
+
+ public override int GetHashCode()
+ {
+ HashCodeCombiner combiner = new HashCodeCombiner();
+ Elements.ForEach(combiner.AddFingerprint);
+ return combiner.CombinedHash;
+ }
+ }
+}
diff --git a/src/System.Web.Mvc/ExpressionUtil/FingerprintingExpressionVisitor.cs b/src/System.Web.Mvc/ExpressionUtil/FingerprintingExpressionVisitor.cs
new file mode 100644
index 00000000..ceaa6987
--- /dev/null
+++ b/src/System.Web.Mvc/ExpressionUtil/FingerprintingExpressionVisitor.cs
@@ -0,0 +1,296 @@
+using System.Collections.Generic;
+using System.Linq.Expressions;
+
+namespace System.Web.Mvc.ExpressionUtil
+{
+ // This is a visitor which produces a fingerprint of an expression. It doesn't
+ // rewrite the expression in a form which can be compiled and cached.
+
+ internal sealed class FingerprintingExpressionVisitor : ExpressionVisitor
+ {
+ private readonly List<object> _seenConstants = new List<object>();
+ private readonly List<ParameterExpression> _seenParameters = new List<ParameterExpression>();
+ private readonly ExpressionFingerprintChain _currentChain = new ExpressionFingerprintChain();
+ private bool _gaveUp;
+
+ private FingerprintingExpressionVisitor()
+ {
+ }
+
+ private T GiveUp<T>(T node)
+ {
+ // We don't understand this node, so just quit.
+
+ _gaveUp = true;
+ return node;
+ }
+
+ // Returns the fingerprint chain + captured constants list for this expression, or null
+ // if the expression couldn't be fingerprinted.
+ public static ExpressionFingerprintChain GetFingerprintChain(Expression expr, out List<object> capturedConstants)
+ {
+ FingerprintingExpressionVisitor visitor = new FingerprintingExpressionVisitor();
+ visitor.Visit(expr);
+
+ if (visitor._gaveUp)
+ {
+ capturedConstants = null;
+ return null;
+ }
+ else
+ {
+ capturedConstants = visitor._seenConstants;
+ return visitor._currentChain;
+ }
+ }
+
+ public override Expression Visit(Expression node)
+ {
+ if (node == null)
+ {
+ _currentChain.Elements.Add(null);
+ return null;
+ }
+ else
+ {
+ return base.Visit(node);
+ }
+ }
+
+ protected override Expression VisitBinary(BinaryExpression node)
+ {
+ if (_gaveUp)
+ {
+ return node;
+ }
+ _currentChain.Elements.Add(new BinaryExpressionFingerprint(node.NodeType, node.Type, node.Method));
+ return base.VisitBinary(node);
+ }
+
+ protected override Expression VisitBlock(BlockExpression node)
+ {
+ return GiveUp(node);
+ }
+
+ protected override CatchBlock VisitCatchBlock(CatchBlock node)
+ {
+ return GiveUp(node);
+ }
+
+ protected override Expression VisitConditional(ConditionalExpression node)
+ {
+ if (_gaveUp)
+ {
+ return node;
+ }
+ _currentChain.Elements.Add(new ConditionalExpressionFingerprint(node.NodeType, node.Type));
+ return base.VisitConditional(node);
+ }
+
+ protected override Expression VisitConstant(ConstantExpression node)
+ {
+ if (_gaveUp)
+ {
+ return node;
+ }
+
+ _seenConstants.Add(node.Value);
+ _currentChain.Elements.Add(new ConstantExpressionFingerprint(node.NodeType, node.Type));
+ return base.VisitConstant(node);
+ }
+
+ protected override Expression VisitDebugInfo(DebugInfoExpression node)
+ {
+ return GiveUp(node);
+ }
+
+ protected override Expression VisitDefault(DefaultExpression node)
+ {
+ if (_gaveUp)
+ {
+ return node;
+ }
+ _currentChain.Elements.Add(new DefaultExpressionFingerprint(node.NodeType, node.Type));
+ return base.VisitDefault(node);
+ }
+
+ protected override Expression VisitDynamic(DynamicExpression node)
+ {
+ return GiveUp(node);
+ }
+
+ protected override ElementInit VisitElementInit(ElementInit node)
+ {
+ return GiveUp(node);
+ }
+
+ protected override Expression VisitExtension(Expression node)
+ {
+ return GiveUp(node);
+ }
+
+ protected override Expression VisitGoto(GotoExpression node)
+ {
+ return GiveUp(node);
+ }
+
+ protected override Expression VisitIndex(IndexExpression node)
+ {
+ if (_gaveUp)
+ {
+ return node;
+ }
+ _currentChain.Elements.Add(new IndexExpressionFingerprint(node.NodeType, node.Type, node.Indexer));
+ return base.VisitIndex(node);
+ }
+
+ protected override Expression VisitInvocation(InvocationExpression node)
+ {
+ return GiveUp(node);
+ }
+
+ protected override Expression VisitLabel(LabelExpression node)
+ {
+ return GiveUp(node);
+ }
+
+ protected override LabelTarget VisitLabelTarget(LabelTarget node)
+ {
+ return GiveUp(node);
+ }
+
+ protected override Expression VisitLambda<T>(Expression<T> node)
+ {
+ if (_gaveUp)
+ {
+ return node;
+ }
+ _currentChain.Elements.Add(new LambdaExpressionFingerprint(node.NodeType, node.Type));
+ return base.VisitLambda<T>(node);
+ }
+
+ protected override Expression VisitListInit(ListInitExpression node)
+ {
+ return GiveUp(node);
+ }
+
+ protected override Expression VisitLoop(LoopExpression node)
+ {
+ return GiveUp(node);
+ }
+
+ protected override Expression VisitMember(MemberExpression node)
+ {
+ if (_gaveUp)
+ {
+ return node;
+ }
+ _currentChain.Elements.Add(new MemberExpressionFingerprint(node.NodeType, node.Type, node.Member));
+ return base.VisitMember(node);
+ }
+
+ protected override MemberAssignment VisitMemberAssignment(MemberAssignment node)
+ {
+ return GiveUp(node);
+ }
+
+ protected override MemberBinding VisitMemberBinding(MemberBinding node)
+ {
+ return GiveUp(node);
+ }
+
+ protected override Expression VisitMemberInit(MemberInitExpression node)
+ {
+ return GiveUp(node);
+ }
+
+ protected override MemberListBinding VisitMemberListBinding(MemberListBinding node)
+ {
+ return GiveUp(node);
+ }
+
+ protected override MemberMemberBinding VisitMemberMemberBinding(MemberMemberBinding node)
+ {
+ return GiveUp(node);
+ }
+
+ protected override Expression VisitMethodCall(MethodCallExpression node)
+ {
+ if (_gaveUp)
+ {
+ return node;
+ }
+ _currentChain.Elements.Add(new MethodCallExpressionFingerprint(node.NodeType, node.Type, node.Method));
+ return base.VisitMethodCall(node);
+ }
+
+ protected override Expression VisitNew(NewExpression node)
+ {
+ return GiveUp(node);
+ }
+
+ protected override Expression VisitNewArray(NewArrayExpression node)
+ {
+ return GiveUp(node);
+ }
+
+ protected override Expression VisitParameter(ParameterExpression node)
+ {
+ if (_gaveUp)
+ {
+ return node;
+ }
+
+ int parameterIndex = _seenParameters.IndexOf(node);
+ if (parameterIndex < 0)
+ {
+ // first time seeing this parameter
+ parameterIndex = _seenParameters.Count;
+ _seenParameters.Add(node);
+ }
+
+ _currentChain.Elements.Add(new ParameterExpressionFingerprint(node.NodeType, node.Type, parameterIndex));
+ return base.VisitParameter(node);
+ }
+
+ protected override Expression VisitRuntimeVariables(RuntimeVariablesExpression node)
+ {
+ return GiveUp(node);
+ }
+
+ protected override Expression VisitSwitch(SwitchExpression node)
+ {
+ return GiveUp(node);
+ }
+
+ protected override SwitchCase VisitSwitchCase(SwitchCase node)
+ {
+ return GiveUp(node);
+ }
+
+ protected override Expression VisitTry(TryExpression node)
+ {
+ return GiveUp(node);
+ }
+
+ protected override Expression VisitTypeBinary(TypeBinaryExpression node)
+ {
+ if (_gaveUp)
+ {
+ return node;
+ }
+ _currentChain.Elements.Add(new TypeBinaryExpressionFingerprint(node.NodeType, node.Type, node.TypeOperand));
+ return base.VisitTypeBinary(node);
+ }
+
+ protected override Expression VisitUnary(UnaryExpression node)
+ {
+ if (_gaveUp)
+ {
+ return node;
+ }
+ _currentChain.Elements.Add(new UnaryExpressionFingerprint(node.NodeType, node.Type, node.Method));
+ return base.VisitUnary(node);
+ }
+ }
+}
diff --git a/src/System.Web.Mvc/ExpressionUtil/HashCodeCombiner.cs b/src/System.Web.Mvc/ExpressionUtil/HashCodeCombiner.cs
new file mode 100644
index 00000000..37349bfc
--- /dev/null
+++ b/src/System.Web.Mvc/ExpressionUtil/HashCodeCombiner.cs
@@ -0,0 +1,56 @@
+using System.Collections;
+
+namespace System.Web.Mvc.ExpressionUtil
+{
+ // based on System.Web.Util.HashCodeCombiner
+ internal class HashCodeCombiner
+ {
+ private long _combinedHash64 = 0x1505L;
+
+ public int CombinedHash
+ {
+ get { return _combinedHash64.GetHashCode(); }
+ }
+
+ public void AddFingerprint(ExpressionFingerprint fingerprint)
+ {
+ if (fingerprint != null)
+ {
+ fingerprint.AddToHashCodeCombiner(this);
+ }
+ else
+ {
+ AddInt32(0);
+ }
+ }
+
+ public void AddEnumerable(IEnumerable e)
+ {
+ if (e == null)
+ {
+ AddInt32(0);
+ }
+ else
+ {
+ int count = 0;
+ foreach (object o in e)
+ {
+ AddObject(o);
+ count++;
+ }
+ AddInt32(count);
+ }
+ }
+
+ public void AddInt32(int i)
+ {
+ _combinedHash64 = ((_combinedHash64 << 5) + _combinedHash64) ^ i;
+ }
+
+ public void AddObject(object o)
+ {
+ int hashCode = (o != null) ? o.GetHashCode() : 0;
+ AddInt32(hashCode);
+ }
+ }
+}
diff --git a/src/System.Web.Mvc/ExpressionUtil/Hoisted`2.cs b/src/System.Web.Mvc/ExpressionUtil/Hoisted`2.cs
new file mode 100644
index 00000000..1785e627
--- /dev/null
+++ b/src/System.Web.Mvc/ExpressionUtil/Hoisted`2.cs
@@ -0,0 +1,6 @@
+using System.Collections.Generic;
+
+namespace System.Web.Mvc.ExpressionUtil
+{
+ internal delegate TValue Hoisted<TModel, TValue>(TModel model, List<object> capturedConstants);
+}
diff --git a/src/System.Web.Mvc/ExpressionUtil/HoistingExpressionVisitor.cs b/src/System.Web.Mvc/ExpressionUtil/HoistingExpressionVisitor.cs
new file mode 100644
index 00000000..40772cbc
--- /dev/null
+++ b/src/System.Web.Mvc/ExpressionUtil/HoistingExpressionVisitor.cs
@@ -0,0 +1,35 @@
+using System.Collections.Generic;
+using System.Linq.Expressions;
+
+namespace System.Web.Mvc.ExpressionUtil
+{
+ // This is a visitor which rewrites constant expressions as parameter lookups. It's meant
+ // to produce an expression which can be cached safely.
+
+ internal sealed class HoistingExpressionVisitor<TIn, TOut> : ExpressionVisitor
+ {
+ private static readonly ParameterExpression _hoistedConstantsParamExpr = Expression.Parameter(typeof(List<object>), "hoistedConstants");
+ private int _numConstantsProcessed;
+
+ // factory will create instance
+ private HoistingExpressionVisitor()
+ {
+ }
+
+ public static Expression<Hoisted<TIn, TOut>> Hoist(Expression<Func<TIn, TOut>> expr)
+ {
+ // rewrite Expression<Func<TIn, TOut>> as Expression<Hoisted<TIn, TOut>>
+
+ var visitor = new HoistingExpressionVisitor<TIn, TOut>();
+ var rewrittenBodyExpr = visitor.Visit(expr.Body);
+ var rewrittenLambdaExpr = Expression.Lambda<Hoisted<TIn, TOut>>(rewrittenBodyExpr, expr.Parameters[0], _hoistedConstantsParamExpr);
+ return rewrittenLambdaExpr;
+ }
+
+ protected override Expression VisitConstant(ConstantExpression node)
+ {
+ // rewrite the constant expression as (TConst)hoistedConstants[i];
+ return Expression.Convert(Expression.Property(_hoistedConstantsParamExpr, "Item", Expression.Constant(_numConstantsProcessed++)), node.Type);
+ }
+ }
+}
diff --git a/src/System.Web.Mvc/ExpressionUtil/IndexExpressionFingerprint.cs b/src/System.Web.Mvc/ExpressionUtil/IndexExpressionFingerprint.cs
new file mode 100644
index 00000000..038e2f53
--- /dev/null
+++ b/src/System.Web.Mvc/ExpressionUtil/IndexExpressionFingerprint.cs
@@ -0,0 +1,41 @@
+using System.Diagnostics.CodeAnalysis;
+using System.Linq.Expressions;
+using System.Reflection;
+
+#pragma warning disable 659 // overrides AddToHashCodeCombiner instead
+
+namespace System.Web.Mvc.ExpressionUtil
+{
+ // IndexExpression fingerprint class
+ // Represents certain forms of array access or indexer property access
+
+ [SuppressMessage("Microsoft.Usage", "CA2218:OverrideGetHashCodeOnOverridingEquals", Justification = "Overrides AddToHashCodeCombiner() instead.")]
+ internal sealed class IndexExpressionFingerprint : ExpressionFingerprint
+ {
+ public IndexExpressionFingerprint(ExpressionType nodeType, Type type, PropertyInfo indexer)
+ : base(nodeType, type)
+ {
+ // Other properties on IndexExpression (like the argument count) are simply derived
+ // from Type and Indexer, so they're not necessary for inclusion in the fingerprint.
+
+ Indexer = indexer;
+ }
+
+ // http://msdn.microsoft.com/en-us/library/system.linq.expressions.indexexpression.indexer.aspx
+ public PropertyInfo Indexer { get; private set; }
+
+ public override bool Equals(object obj)
+ {
+ IndexExpressionFingerprint other = obj as IndexExpressionFingerprint;
+ return (other != null)
+ && Equals(this.Indexer, other.Indexer)
+ && this.Equals(other);
+ }
+
+ internal override void AddToHashCodeCombiner(HashCodeCombiner combiner)
+ {
+ combiner.AddObject(Indexer);
+ base.AddToHashCodeCombiner(combiner);
+ }
+ }
+}
diff --git a/src/System.Web.Mvc/ExpressionUtil/LambdaExpressionFingerprint.cs b/src/System.Web.Mvc/ExpressionUtil/LambdaExpressionFingerprint.cs
new file mode 100644
index 00000000..1bcb58bf
--- /dev/null
+++ b/src/System.Web.Mvc/ExpressionUtil/LambdaExpressionFingerprint.cs
@@ -0,0 +1,28 @@
+using System.Diagnostics.CodeAnalysis;
+using System.Linq.Expressions;
+
+#pragma warning disable 659 // overrides AddToHashCodeCombiner instead
+
+namespace System.Web.Mvc.ExpressionUtil
+{
+ // LambdaExpression fingerprint class
+ // Represents a lambda expression (root element in Expression<T>)
+
+ [SuppressMessage("Microsoft.Usage", "CA2218:OverrideGetHashCodeOnOverridingEquals", Justification = "Overrides AddToHashCodeCombiner() instead.")]
+ internal sealed class LambdaExpressionFingerprint : ExpressionFingerprint
+ {
+ public LambdaExpressionFingerprint(ExpressionType nodeType, Type type)
+ : base(nodeType, type)
+ {
+ // There are no properties on LambdaExpression that are worth including in
+ // the fingerprint.
+ }
+
+ public override bool Equals(object obj)
+ {
+ LambdaExpressionFingerprint other = obj as LambdaExpressionFingerprint;
+ return (other != null)
+ && this.Equals(other);
+ }
+ }
+}
diff --git a/src/System.Web.Mvc/ExpressionUtil/MemberExpressionFingerprint.cs b/src/System.Web.Mvc/ExpressionUtil/MemberExpressionFingerprint.cs
new file mode 100644
index 00000000..309dedca
--- /dev/null
+++ b/src/System.Web.Mvc/ExpressionUtil/MemberExpressionFingerprint.cs
@@ -0,0 +1,38 @@
+using System.Diagnostics.CodeAnalysis;
+using System.Linq.Expressions;
+using System.Reflection;
+
+#pragma warning disable 659 // overrides AddToHashCodeCombiner instead
+
+namespace System.Web.Mvc.ExpressionUtil
+{
+ // MemberExpression fingerprint class
+ // Expression of form xxx.FieldOrProperty
+
+ [SuppressMessage("Microsoft.Usage", "CA2218:OverrideGetHashCodeOnOverridingEquals", Justification = "Overrides AddToHashCodeCombiner() instead.")]
+ internal sealed class MemberExpressionFingerprint : ExpressionFingerprint
+ {
+ public MemberExpressionFingerprint(ExpressionType nodeType, Type type, MemberInfo member)
+ : base(nodeType, type)
+ {
+ Member = member;
+ }
+
+ // http://msdn.microsoft.com/en-us/library/system.linq.expressions.memberexpression.member.aspx
+ public MemberInfo Member { get; private set; }
+
+ public override bool Equals(object obj)
+ {
+ MemberExpressionFingerprint other = obj as MemberExpressionFingerprint;
+ return (other != null)
+ && Equals(this.Member, other.Member)
+ && this.Equals(other);
+ }
+
+ internal override void AddToHashCodeCombiner(HashCodeCombiner combiner)
+ {
+ combiner.AddObject(Member);
+ base.AddToHashCodeCombiner(combiner);
+ }
+ }
+}
diff --git a/src/System.Web.Mvc/ExpressionUtil/MethodCallExpressionFingerprint.cs b/src/System.Web.Mvc/ExpressionUtil/MethodCallExpressionFingerprint.cs
new file mode 100644
index 00000000..082541a8
--- /dev/null
+++ b/src/System.Web.Mvc/ExpressionUtil/MethodCallExpressionFingerprint.cs
@@ -0,0 +1,41 @@
+using System.Diagnostics.CodeAnalysis;
+using System.Linq.Expressions;
+using System.Reflection;
+
+#pragma warning disable 659 // overrides AddToHashCodeCombiner instead
+
+namespace System.Web.Mvc.ExpressionUtil
+{
+ // MethodCallExpression fingerprint class
+ // Expression of form xxx.Foo(...), xxx[...] (get_Item()), etc.
+
+ [SuppressMessage("Microsoft.Usage", "CA2218:OverrideGetHashCodeOnOverridingEquals", Justification = "Overrides AddToHashCodeCombiner() instead.")]
+ internal sealed class MethodCallExpressionFingerprint : ExpressionFingerprint
+ {
+ public MethodCallExpressionFingerprint(ExpressionType nodeType, Type type, MethodInfo method)
+ : base(nodeType, type)
+ {
+ // Other properties on MethodCallExpression (like the argument count) are simply derived
+ // from Type and Indexer, so they're not necessary for inclusion in the fingerprint.
+
+ Method = method;
+ }
+
+ // http://msdn.microsoft.com/en-us/library/system.linq.expressions.methodcallexpression.method.aspx
+ public MethodInfo Method { get; private set; }
+
+ public override bool Equals(object obj)
+ {
+ MethodCallExpressionFingerprint other = obj as MethodCallExpressionFingerprint;
+ return (other != null)
+ && Equals(this.Method, other.Method)
+ && this.Equals(other);
+ }
+
+ internal override void AddToHashCodeCombiner(HashCodeCombiner combiner)
+ {
+ combiner.AddObject(Method);
+ base.AddToHashCodeCombiner(combiner);
+ }
+ }
+}
diff --git a/src/System.Web.Mvc/ExpressionUtil/ParameterExpressionFingerprint.cs b/src/System.Web.Mvc/ExpressionUtil/ParameterExpressionFingerprint.cs
new file mode 100644
index 00000000..5799d064
--- /dev/null
+++ b/src/System.Web.Mvc/ExpressionUtil/ParameterExpressionFingerprint.cs
@@ -0,0 +1,37 @@
+using System.Diagnostics.CodeAnalysis;
+using System.Linq.Expressions;
+
+#pragma warning disable 659 // overrides AddToHashCodeCombiner instead
+
+namespace System.Web.Mvc.ExpressionUtil
+{
+ // ParameterExpression fingerprint class
+ // Can represent the model parameter or an inner parameter in an open lambda expression
+
+ [SuppressMessage("Microsoft.Usage", "CA2218:OverrideGetHashCodeOnOverridingEquals", Justification = "Overrides AddToHashCodeCombiner() instead.")]
+ internal sealed class ParameterExpressionFingerprint : ExpressionFingerprint
+ {
+ public ParameterExpressionFingerprint(ExpressionType nodeType, Type type, int parameterIndex)
+ : base(nodeType, type)
+ {
+ ParameterIndex = parameterIndex;
+ }
+
+ // Parameter position within the overall expression, used to maintain alpha equivalence.
+ public int ParameterIndex { get; private set; }
+
+ public override bool Equals(object obj)
+ {
+ ParameterExpressionFingerprint other = obj as ParameterExpressionFingerprint;
+ return (other != null)
+ && (this.ParameterIndex == other.ParameterIndex)
+ && this.Equals(other);
+ }
+
+ internal override void AddToHashCodeCombiner(HashCodeCombiner combiner)
+ {
+ combiner.AddInt32(ParameterIndex);
+ base.AddToHashCodeCombiner(combiner);
+ }
+ }
+}
diff --git a/src/System.Web.Mvc/ExpressionUtil/TypeBinaryExpressionFingerprint.cs b/src/System.Web.Mvc/ExpressionUtil/TypeBinaryExpressionFingerprint.cs
new file mode 100644
index 00000000..6e60efd4
--- /dev/null
+++ b/src/System.Web.Mvc/ExpressionUtil/TypeBinaryExpressionFingerprint.cs
@@ -0,0 +1,37 @@
+using System.Diagnostics.CodeAnalysis;
+using System.Linq.Expressions;
+
+#pragma warning disable 659 // overrides AddToHashCodeCombiner instead
+
+namespace System.Web.Mvc.ExpressionUtil
+{
+ // TypeBinary fingerprint class
+ // Expression of form "obj is T"
+
+ [SuppressMessage("Microsoft.Usage", "CA2218:OverrideGetHashCodeOnOverridingEquals", Justification = "Overrides AddToHashCodeCombiner() instead.")]
+ internal sealed class TypeBinaryExpressionFingerprint : ExpressionFingerprint
+ {
+ public TypeBinaryExpressionFingerprint(ExpressionType nodeType, Type type, Type typeOperand)
+ : base(nodeType, type)
+ {
+ TypeOperand = typeOperand;
+ }
+
+ // http://msdn.microsoft.com/en-us/library/system.linq.expressions.typebinaryexpression.typeoperand.aspx
+ public Type TypeOperand { get; private set; }
+
+ public override bool Equals(object obj)
+ {
+ TypeBinaryExpressionFingerprint other = obj as TypeBinaryExpressionFingerprint;
+ return (other != null)
+ && Equals(this.TypeOperand, other.TypeOperand)
+ && this.Equals(other);
+ }
+
+ internal override void AddToHashCodeCombiner(HashCodeCombiner combiner)
+ {
+ combiner.AddObject(TypeOperand);
+ base.AddToHashCodeCombiner(combiner);
+ }
+ }
+}
diff --git a/src/System.Web.Mvc/ExpressionUtil/UnaryExpressionFingerprint.cs b/src/System.Web.Mvc/ExpressionUtil/UnaryExpressionFingerprint.cs
new file mode 100644
index 00000000..2df90a8a
--- /dev/null
+++ b/src/System.Web.Mvc/ExpressionUtil/UnaryExpressionFingerprint.cs
@@ -0,0 +1,41 @@
+using System.Diagnostics.CodeAnalysis;
+using System.Linq.Expressions;
+using System.Reflection;
+
+#pragma warning disable 659 // overrides AddToHashCodeCombiner instead
+
+namespace System.Web.Mvc.ExpressionUtil
+{
+ // UnaryExpression fingerprint class
+ // The most common appearance of a UnaryExpression is a cast or other conversion operator
+
+ [SuppressMessage("Microsoft.Usage", "CA2218:OverrideGetHashCodeOnOverridingEquals", Justification = "Overrides AddToHashCodeCombiner() instead.")]
+ internal sealed class UnaryExpressionFingerprint : ExpressionFingerprint
+ {
+ public UnaryExpressionFingerprint(ExpressionType nodeType, Type type, MethodInfo method)
+ : base(nodeType, type)
+ {
+ // Other properties on UnaryExpression (like IsLifted / IsLiftedToNull) are simply derived
+ // from Type and NodeType, so they're not necessary for inclusion in the fingerprint.
+
+ Method = method;
+ }
+
+ // http://msdn.microsoft.com/en-us/library/system.linq.expressions.unaryexpression.method.aspx
+ public MethodInfo Method { get; private set; }
+
+ public override bool Equals(object obj)
+ {
+ UnaryExpressionFingerprint other = obj as UnaryExpressionFingerprint;
+ return (other != null)
+ && Equals(this.Method, other.Method)
+ && this.Equals(other);
+ }
+
+ internal override void AddToHashCodeCombiner(HashCodeCombiner combiner)
+ {
+ combiner.AddObject(Method);
+ base.AddToHashCodeCombiner(combiner);
+ }
+ }
+}
diff --git a/src/System.Web.Mvc/FieldValidationMetadata.cs b/src/System.Web.Mvc/FieldValidationMetadata.cs
new file mode 100644
index 00000000..e025e6ac
--- /dev/null
+++ b/src/System.Web.Mvc/FieldValidationMetadata.cs
@@ -0,0 +1,26 @@
+using System.Collections.Generic;
+using System.Collections.ObjectModel;
+
+namespace System.Web.Mvc
+{
+ public class FieldValidationMetadata
+ {
+ private readonly Collection<ModelClientValidationRule> _validationRules = new Collection<ModelClientValidationRule>();
+ private string _fieldName;
+
+ public string FieldName
+ {
+ get { return _fieldName ?? String.Empty; }
+ set { _fieldName = value; }
+ }
+
+ public bool ReplaceValidationMessageContents { get; set; }
+
+ public string ValidationMessageId { get; set; }
+
+ public ICollection<ModelClientValidationRule> ValidationRules
+ {
+ get { return _validationRules; }
+ }
+ }
+}
diff --git a/src/System.Web.Mvc/FileContentResult.cs b/src/System.Web.Mvc/FileContentResult.cs
new file mode 100644
index 00000000..ab28d0ba
--- /dev/null
+++ b/src/System.Web.Mvc/FileContentResult.cs
@@ -0,0 +1,26 @@
+using System.Diagnostics.CodeAnalysis;
+
+namespace System.Web.Mvc
+{
+ public class FileContentResult : FileResult
+ {
+ public FileContentResult(byte[] fileContents, string contentType)
+ : base(contentType)
+ {
+ if (fileContents == null)
+ {
+ throw new ArgumentNullException("fileContents");
+ }
+
+ FileContents = fileContents;
+ }
+
+ [SuppressMessage("Microsoft.Performance", "CA1819:PropertiesShouldNotReturnArrays", Justification = "There's no reason to tamper-proof this array since it's supplied to the type's constructor.")]
+ public byte[] FileContents { get; private set; }
+
+ protected override void WriteFile(HttpResponseBase response)
+ {
+ response.OutputStream.Write(FileContents, 0, FileContents.Length);
+ }
+ }
+}
diff --git a/src/System.Web.Mvc/FilePathResult.cs b/src/System.Web.Mvc/FilePathResult.cs
new file mode 100644
index 00000000..fd2507a6
--- /dev/null
+++ b/src/System.Web.Mvc/FilePathResult.cs
@@ -0,0 +1,25 @@
+using System.Web.Mvc.Properties;
+
+namespace System.Web.Mvc
+{
+ public class FilePathResult : FileResult
+ {
+ public FilePathResult(string fileName, string contentType)
+ : base(contentType)
+ {
+ if (String.IsNullOrEmpty(fileName))
+ {
+ throw new ArgumentException(MvcResources.Common_NullOrEmpty, "fileName");
+ }
+
+ FileName = fileName;
+ }
+
+ public string FileName { get; private set; }
+
+ protected override void WriteFile(HttpResponseBase response)
+ {
+ response.TransmitFile(FileName);
+ }
+ }
+}
diff --git a/src/System.Web.Mvc/FileResult.cs b/src/System.Web.Mvc/FileResult.cs
new file mode 100644
index 00000000..dcaab762
--- /dev/null
+++ b/src/System.Web.Mvc/FileResult.cs
@@ -0,0 +1,143 @@
+using System.Net.Mime;
+using System.Text;
+using System.Web.Mvc.Properties;
+
+namespace System.Web.Mvc
+{
+ public abstract class FileResult : ActionResult
+ {
+ private string _fileDownloadName;
+
+ protected FileResult(string contentType)
+ {
+ if (String.IsNullOrEmpty(contentType))
+ {
+ throw new ArgumentException(MvcResources.Common_NullOrEmpty, "contentType");
+ }
+
+ ContentType = contentType;
+ }
+
+ public string ContentType { get; private set; }
+
+ public string FileDownloadName
+ {
+ get { return _fileDownloadName ?? String.Empty; }
+ set { _fileDownloadName = value; }
+ }
+
+ public override void ExecuteResult(ControllerContext context)
+ {
+ if (context == null)
+ {
+ throw new ArgumentNullException("context");
+ }
+
+ HttpResponseBase response = context.HttpContext.Response;
+ response.ContentType = ContentType;
+
+ if (!String.IsNullOrEmpty(FileDownloadName))
+ {
+ // From RFC 2183, Sec. 2.3:
+ // The sender may want to suggest a filename to be used if the entity is
+ // detached and stored in a separate file. If the receiving MUA writes
+ // the entity to a file, the suggested filename should be used as a
+ // basis for the actual filename, where possible.
+ string headerValue = ContentDispositionUtil.GetHeaderValue(FileDownloadName);
+ context.HttpContext.Response.AddHeader("Content-Disposition", headerValue);
+ }
+
+ WriteFile(response);
+ }
+
+ protected abstract void WriteFile(HttpResponseBase response);
+
+ private static class ContentDispositionUtil
+ {
+ private const string HexDigits = "0123456789ABCDEF";
+
+ private static void AddByteToStringBuilder(byte b, StringBuilder builder)
+ {
+ builder.Append('%');
+
+ int i = b;
+ AddHexDigitToStringBuilder(i >> 4, builder);
+ AddHexDigitToStringBuilder(i % 16, builder);
+ }
+
+ private static void AddHexDigitToStringBuilder(int digit, StringBuilder builder)
+ {
+ builder.Append(HexDigits[digit]);
+ }
+
+ private static string CreateRfc2231HeaderValue(string filename)
+ {
+ StringBuilder builder = new StringBuilder("attachment; filename*=UTF-8''");
+
+ byte[] filenameBytes = Encoding.UTF8.GetBytes(filename);
+ foreach (byte b in filenameBytes)
+ {
+ if (IsByteValidHeaderValueCharacter(b))
+ {
+ builder.Append((char)b);
+ }
+ else
+ {
+ AddByteToStringBuilder(b, builder);
+ }
+ }
+
+ return builder.ToString();
+ }
+
+ public static string GetHeaderValue(string fileName)
+ {
+ try
+ {
+ // first, try using the .NET built-in generator
+ ContentDisposition disposition = new ContentDisposition() { FileName = fileName };
+ return disposition.ToString();
+ }
+ catch (FormatException)
+ {
+ // otherwise, fall back to RFC 2231 extensions generator
+ return CreateRfc2231HeaderValue(fileName);
+ }
+ }
+
+ // Application of RFC 2231 Encoding to Hypertext Transfer Protocol (HTTP) Header Fields, sec. 3.2
+ // http://greenbytes.de/tech/webdav/draft-reschke-rfc2231-in-http-latest.html
+ private static bool IsByteValidHeaderValueCharacter(byte b)
+ {
+ if ((byte)'0' <= b && b <= (byte)'9')
+ {
+ return true; // is digit
+ }
+ if ((byte)'a' <= b && b <= (byte)'z')
+ {
+ return true; // lowercase letter
+ }
+ if ((byte)'A' <= b && b <= (byte)'Z')
+ {
+ return true; // uppercase letter
+ }
+
+ switch (b)
+ {
+ case (byte)'-':
+ case (byte)'.':
+ case (byte)'_':
+ case (byte)'~':
+ case (byte)':':
+ case (byte)'!':
+ case (byte)'$':
+ case (byte)'&':
+ case (byte)'+':
+ return true;
+ }
+
+ return false;
+ }
+ }
+ }
+}
diff --git a/src/System.Web.Mvc/FileStreamResult.cs b/src/System.Web.Mvc/FileStreamResult.cs
new file mode 100644
index 00000000..2fc5a132
--- /dev/null
+++ b/src/System.Web.Mvc/FileStreamResult.cs
@@ -0,0 +1,45 @@
+using System.IO;
+
+namespace System.Web.Mvc
+{
+ public class FileStreamResult : FileResult
+ {
+ // default buffer size as defined in BufferedStream type
+ private const int BufferSize = 0x1000;
+
+ public FileStreamResult(Stream fileStream, string contentType)
+ : base(contentType)
+ {
+ if (fileStream == null)
+ {
+ throw new ArgumentNullException("fileStream");
+ }
+
+ FileStream = fileStream;
+ }
+
+ public Stream FileStream { get; private set; }
+
+ protected override void WriteFile(HttpResponseBase response)
+ {
+ // grab chunks of data and write to the output stream
+ Stream outputStream = response.OutputStream;
+ using (FileStream)
+ {
+ byte[] buffer = new byte[BufferSize];
+
+ while (true)
+ {
+ int bytesRead = FileStream.Read(buffer, 0, BufferSize);
+ if (bytesRead == 0)
+ {
+ // no more data
+ break;
+ }
+
+ outputStream.Write(buffer, 0, bytesRead);
+ }
+ }
+ }
+ }
+}
diff --git a/src/System.Web.Mvc/Filter.cs b/src/System.Web.Mvc/Filter.cs
new file mode 100644
index 00000000..e027dbef
--- /dev/null
+++ b/src/System.Web.Mvc/Filter.cs
@@ -0,0 +1,34 @@
+namespace System.Web.Mvc
+{
+ public class Filter
+ {
+ public const int DefaultOrder = -1;
+
+ public Filter(object instance, FilterScope scope, int? order)
+ {
+ if (instance == null)
+ {
+ throw new ArgumentNullException("instance");
+ }
+
+ if (order == null)
+ {
+ IMvcFilter mvcFilter = instance as IMvcFilter;
+ if (mvcFilter != null)
+ {
+ order = mvcFilter.Order;
+ }
+ }
+
+ Instance = instance;
+ Order = order ?? DefaultOrder;
+ Scope = scope;
+ }
+
+ public object Instance { get; protected set; }
+
+ public int Order { get; protected set; }
+
+ public FilterScope Scope { get; protected set; }
+ }
+}
diff --git a/src/System.Web.Mvc/FilterAttribute.cs b/src/System.Web.Mvc/FilterAttribute.cs
new file mode 100644
index 00000000..88fcdaf4
--- /dev/null
+++ b/src/System.Web.Mvc/FilterAttribute.cs
@@ -0,0 +1,41 @@
+using System.Collections.Concurrent;
+using System.Linq;
+using System.Web.Mvc.Properties;
+
+namespace System.Web.Mvc
+{
+ [AttributeUsage(AttributeTargets.Class | AttributeTargets.Method, Inherited = true, AllowMultiple = false)]
+ public abstract class FilterAttribute : Attribute, IMvcFilter
+ {
+ private static readonly ConcurrentDictionary<Type, bool> _multiuseAttributeCache = new ConcurrentDictionary<Type, bool>();
+ private int _order = Filter.DefaultOrder;
+
+ public bool AllowMultiple
+ {
+ get { return AllowsMultiple(GetType()); }
+ }
+
+ public int Order
+ {
+ get { return _order; }
+ set
+ {
+ if (value < Filter.DefaultOrder)
+ {
+ throw new ArgumentOutOfRangeException("value", MvcResources.FilterAttribute_OrderOutOfRange);
+ }
+ _order = value;
+ }
+ }
+
+ private static bool AllowsMultiple(Type attributeType)
+ {
+ return _multiuseAttributeCache.GetOrAdd(
+ attributeType,
+ type => type.GetCustomAttributes(typeof(AttributeUsageAttribute), true)
+ .Cast<AttributeUsageAttribute>()
+ .First()
+ .AllowMultiple);
+ }
+ }
+}
diff --git a/src/System.Web.Mvc/FilterAttributeFilterProvider.cs b/src/System.Web.Mvc/FilterAttributeFilterProvider.cs
new file mode 100644
index 00000000..74022795
--- /dev/null
+++ b/src/System.Web.Mvc/FilterAttributeFilterProvider.cs
@@ -0,0 +1,46 @@
+using System.Collections.Generic;
+using System.Linq;
+
+namespace System.Web.Mvc
+{
+ public class FilterAttributeFilterProvider : IFilterProvider
+ {
+ private readonly bool _cacheAttributeInstances;
+
+ public FilterAttributeFilterProvider()
+ : this(true)
+ {
+ }
+
+ public FilterAttributeFilterProvider(bool cacheAttributeInstances)
+ {
+ _cacheAttributeInstances = cacheAttributeInstances;
+ }
+
+ protected virtual IEnumerable<FilterAttribute> GetActionAttributes(ControllerContext controllerContext, ActionDescriptor actionDescriptor)
+ {
+ return actionDescriptor.GetFilterAttributes(_cacheAttributeInstances);
+ }
+
+ protected virtual IEnumerable<FilterAttribute> GetControllerAttributes(ControllerContext controllerContext, ActionDescriptor actionDescriptor)
+ {
+ return actionDescriptor.ControllerDescriptor.GetFilterAttributes(_cacheAttributeInstances);
+ }
+
+ public virtual IEnumerable<Filter> GetFilters(ControllerContext controllerContext, ActionDescriptor actionDescriptor)
+ {
+ ControllerBase controller = controllerContext.Controller;
+ if (controller == null)
+ {
+ return Enumerable.Empty<Filter>();
+ }
+
+ var typeFilters = GetControllerAttributes(controllerContext, actionDescriptor)
+ .Select(attr => new Filter(attr, FilterScope.Controller, null));
+ var methodFilters = GetActionAttributes(controllerContext, actionDescriptor)
+ .Select(attr => new Filter(attr, FilterScope.Action, null));
+
+ return typeFilters.Concat(methodFilters).ToList();
+ }
+ }
+}
diff --git a/src/System.Web.Mvc/FilterInfo.cs b/src/System.Web.Mvc/FilterInfo.cs
new file mode 100644
index 00000000..1d14ce79
--- /dev/null
+++ b/src/System.Web.Mvc/FilterInfo.cs
@@ -0,0 +1,48 @@
+using System.Collections.Generic;
+using System.Linq;
+
+namespace System.Web.Mvc
+{
+ public class FilterInfo
+ {
+ private List<IActionFilter> _actionFilters = new List<IActionFilter>();
+ private List<IAuthorizationFilter> _authorizationFilters = new List<IAuthorizationFilter>();
+ private List<IExceptionFilter> _exceptionFilters = new List<IExceptionFilter>();
+ private List<IResultFilter> _resultFilters = new List<IResultFilter>();
+
+ public FilterInfo()
+ {
+ }
+
+ public FilterInfo(IEnumerable<Filter> filters)
+ {
+ // evaluate the 'filters' enumerable only once since the operation can be quite expensive
+ var filterInstances = filters.Select(f => f.Instance).ToList();
+
+ _actionFilters.AddRange(filterInstances.OfType<IActionFilter>());
+ _authorizationFilters.AddRange(filterInstances.OfType<IAuthorizationFilter>());
+ _exceptionFilters.AddRange(filterInstances.OfType<IExceptionFilter>());
+ _resultFilters.AddRange(filterInstances.OfType<IResultFilter>());
+ }
+
+ public IList<IActionFilter> ActionFilters
+ {
+ get { return _actionFilters; }
+ }
+
+ public IList<IAuthorizationFilter> AuthorizationFilters
+ {
+ get { return _authorizationFilters; }
+ }
+
+ public IList<IExceptionFilter> ExceptionFilters
+ {
+ get { return _exceptionFilters; }
+ }
+
+ public IList<IResultFilter> ResultFilters
+ {
+ get { return _resultFilters; }
+ }
+ }
+}
diff --git a/src/System.Web.Mvc/FilterProviderCollection.cs b/src/System.Web.Mvc/FilterProviderCollection.cs
new file mode 100644
index 00000000..854733d7
--- /dev/null
+++ b/src/System.Web.Mvc/FilterProviderCollection.cs
@@ -0,0 +1,125 @@
+using System.Collections.Generic;
+using System.Collections.ObjectModel;
+using System.Linq;
+
+namespace System.Web.Mvc
+{
+ public class FilterProviderCollection : Collection<IFilterProvider>
+ {
+ private static FilterComparer _filterComparer = new FilterComparer();
+ private IResolver<IEnumerable<IFilterProvider>> _serviceResolver;
+
+ public FilterProviderCollection()
+ {
+ _serviceResolver = new MultiServiceResolver<IFilterProvider>(() => Items);
+ }
+
+ public FilterProviderCollection(IList<IFilterProvider> providers)
+ : base(providers)
+ {
+ _serviceResolver = new MultiServiceResolver<IFilterProvider>(() => Items);
+ }
+
+ internal FilterProviderCollection(IResolver<IEnumerable<IFilterProvider>> serviceResolver, params IFilterProvider[] providers)
+ : base(providers)
+ {
+ _serviceResolver = serviceResolver ?? new MultiServiceResolver<IFilterProvider>(() => Items);
+ }
+
+ private IEnumerable<IFilterProvider> CombinedItems
+ {
+ get { return _serviceResolver.Current; }
+ }
+
+ private static bool AllowMultiple(object filterInstance)
+ {
+ IMvcFilter mvcFilter = filterInstance as IMvcFilter;
+ if (mvcFilter == null)
+ {
+ return true;
+ }
+
+ return mvcFilter.AllowMultiple;
+ }
+
+ public IEnumerable<Filter> GetFilters(ControllerContext controllerContext, ActionDescriptor actionDescriptor)
+ {
+ if (controllerContext == null)
+ {
+ throw new ArgumentNullException("controllerContext");
+ }
+ if (actionDescriptor == null)
+ {
+ throw new ArgumentNullException("actionDescriptor");
+ }
+
+ IEnumerable<Filter> combinedFilters =
+ CombinedItems.SelectMany(fp => fp.GetFilters(controllerContext, actionDescriptor))
+ .OrderBy(filter => filter, _filterComparer);
+
+ // Remove duplicates from the back forward
+ return RemoveDuplicates(combinedFilters.Reverse()).Reverse();
+ }
+
+ private IEnumerable<Filter> RemoveDuplicates(IEnumerable<Filter> filters)
+ {
+ HashSet<Type> visitedTypes = new HashSet<Type>();
+
+ foreach (Filter filter in filters)
+ {
+ object filterInstance = filter.Instance;
+ Type filterInstanceType = filterInstance.GetType();
+
+ if (!visitedTypes.Contains(filterInstanceType) || AllowMultiple(filterInstance))
+ {
+ yield return filter;
+ visitedTypes.Add(filterInstanceType);
+ }
+ }
+ }
+
+ private class FilterComparer : IComparer<Filter>
+ {
+ public int Compare(Filter x, Filter y)
+ {
+ // Nulls always have to be less than non-nulls
+ if (x == null && y == null)
+ {
+ return 0;
+ }
+ if (x == null)
+ {
+ return -1;
+ }
+ if (y == null)
+ {
+ return 1;
+ }
+
+ // Sort first by order...
+
+ if (x.Order < y.Order)
+ {
+ return -1;
+ }
+ if (x.Order > y.Order)
+ {
+ return 1;
+ }
+
+ // ...then by scope
+
+ if (x.Scope < y.Scope)
+ {
+ return -1;
+ }
+ if (x.Scope > y.Scope)
+ {
+ return 1;
+ }
+
+ return 0;
+ }
+ }
+ }
+}
diff --git a/src/System.Web.Mvc/FilterProviders.cs b/src/System.Web.Mvc/FilterProviders.cs
new file mode 100644
index 00000000..7ff3db9a
--- /dev/null
+++ b/src/System.Web.Mvc/FilterProviders.cs
@@ -0,0 +1,15 @@
+namespace System.Web.Mvc
+{
+ public static class FilterProviders
+ {
+ static FilterProviders()
+ {
+ Providers = new FilterProviderCollection();
+ Providers.Add(GlobalFilters.Filters);
+ Providers.Add(new FilterAttributeFilterProvider());
+ Providers.Add(new ControllerInstanceFilterProvider());
+ }
+
+ public static FilterProviderCollection Providers { get; private set; }
+ }
+}
diff --git a/src/System.Web.Mvc/FilterScope.cs b/src/System.Web.Mvc/FilterScope.cs
new file mode 100644
index 00000000..ca315e00
--- /dev/null
+++ b/src/System.Web.Mvc/FilterScope.cs
@@ -0,0 +1,11 @@
+namespace System.Web.Mvc
+{
+ public enum FilterScope
+ {
+ First = 0,
+ Global = 10,
+ Controller = 20,
+ Action = 30,
+ Last = 100,
+ }
+}
diff --git a/src/System.Web.Mvc/FormCollection.cs b/src/System.Web.Mvc/FormCollection.cs
new file mode 100644
index 00000000..4adc8b42
--- /dev/null
+++ b/src/System.Web.Mvc/FormCollection.cs
@@ -0,0 +1,96 @@
+using System.Collections.Specialized;
+using System.Diagnostics.CodeAnalysis;
+using System.Globalization;
+using System.Web.Helpers;
+
+namespace System.Web.Mvc
+{
+ [SuppressMessage("Microsoft.Usage", "CA2237:MarkISerializableTypesWithSerializable", Justification = "It is not anticipated that users will need to serialize this type.")]
+ [SuppressMessage("Microsoft.Design", "CA1035:ICollectionImplementationsHaveStronglyTypedMembers", Justification = "It is not anticipated that users will call FormCollection.CopyTo().")]
+ [FormCollectionBinder]
+ public sealed class FormCollection : NameValueCollection, IValueProvider
+ {
+ public FormCollection()
+ {
+ }
+
+ public FormCollection(NameValueCollection collection)
+ {
+ if (collection == null)
+ {
+ throw new ArgumentNullException("collection");
+ }
+
+ Add(collection);
+ }
+
+ internal FormCollection(ControllerBase controller, Func<NameValueCollection> validatedValuesThunk, Func<NameValueCollection> unvalidatedValuesThunk)
+ {
+ Add(controller == null || controller.ValidateRequest ? validatedValuesThunk() : unvalidatedValuesThunk());
+ }
+
+ public ValueProviderResult GetValue(string name)
+ {
+ if (name == null)
+ {
+ throw new ArgumentNullException("name");
+ }
+
+ string[] rawValue = GetValues(name);
+ if (rawValue == null)
+ {
+ return null;
+ }
+
+ string attemptedValue = this[name];
+ return new ValueProviderResult(rawValue, attemptedValue, CultureInfo.CurrentCulture);
+ }
+
+ public IValueProvider ToValueProvider()
+ {
+ return this;
+ }
+
+ #region IValueProvider Members
+
+ bool IValueProvider.ContainsPrefix(string prefix)
+ {
+ return ValueProviderUtil.CollectionContainsPrefix(AllKeys, prefix);
+ }
+
+ ValueProviderResult IValueProvider.GetValue(string key)
+ {
+ return GetValue(key);
+ }
+
+ #endregion
+
+ private sealed class FormCollectionBinderAttribute : CustomModelBinderAttribute
+ {
+ // since the FormCollectionModelBinder.BindModel() method is thread-safe, we only need to keep
+ // a single instance of the binder around
+ private static readonly FormCollectionModelBinder _binder = new FormCollectionModelBinder();
+
+ public override IModelBinder GetBinder()
+ {
+ return _binder;
+ }
+
+ // this class is used for generating a FormCollection object
+ private sealed class FormCollectionModelBinder : IModelBinder
+ {
+ public object BindModel(ControllerContext controllerContext, ModelBindingContext bindingContext)
+ {
+ if (controllerContext == null)
+ {
+ throw new ArgumentNullException("controllerContext");
+ }
+
+ return new FormCollection(controllerContext.Controller,
+ () => controllerContext.HttpContext.Request.Form,
+ () => controllerContext.HttpContext.Request.Unvalidated().Form);
+ }
+ }
+ }
+ }
+}
diff --git a/src/System.Web.Mvc/FormContext.cs b/src/System.Web.Mvc/FormContext.cs
new file mode 100644
index 00000000..b117c2b2
--- /dev/null
+++ b/src/System.Web.Mvc/FormContext.cs
@@ -0,0 +1,81 @@
+using System.Collections.Generic;
+using System.Diagnostics.CodeAnalysis;
+using System.Web.Script.Serialization;
+
+namespace System.Web.Mvc
+{
+ public class FormContext
+ {
+ private readonly Dictionary<string, FieldValidationMetadata> _fieldValidators = new Dictionary<string, FieldValidationMetadata>();
+ private readonly Dictionary<string, bool> _renderedFields = new Dictionary<string, bool>();
+
+ public IDictionary<string, FieldValidationMetadata> FieldValidators
+ {
+ get { return _fieldValidators; }
+ }
+
+ public string FormId { get; set; }
+
+ public bool ReplaceValidationSummary { get; set; }
+
+ public string ValidationSummaryId { get; set; }
+
+ [SuppressMessage("Microsoft.Design", "CA1024:UsePropertiesWhereAppropriate", Justification = "Performs a potentially time-consuming conversion.")]
+ public string GetJsonValidationMetadata()
+ {
+ JavaScriptSerializer serializer = new JavaScriptSerializer();
+
+ SortedDictionary<string, object> dict = new SortedDictionary<string, object>()
+ {
+ { "Fields", FieldValidators.Values },
+ { "FormId", FormId }
+ };
+ if (!String.IsNullOrEmpty(ValidationSummaryId))
+ {
+ dict["ValidationSummaryId"] = ValidationSummaryId;
+ }
+ dict["ReplaceValidationSummary"] = ReplaceValidationSummary;
+
+ return serializer.Serialize(dict);
+ }
+
+ public FieldValidationMetadata GetValidationMetadataForField(string fieldName)
+ {
+ return GetValidationMetadataForField(fieldName, false /* createIfNotFound */);
+ }
+
+ public FieldValidationMetadata GetValidationMetadataForField(string fieldName, bool createIfNotFound)
+ {
+ if (String.IsNullOrEmpty(fieldName))
+ {
+ throw Error.ParameterCannotBeNullOrEmpty("fieldName");
+ }
+
+ FieldValidationMetadata metadata;
+ if (!FieldValidators.TryGetValue(fieldName, out metadata))
+ {
+ if (createIfNotFound)
+ {
+ metadata = new FieldValidationMetadata()
+ {
+ FieldName = fieldName
+ };
+ FieldValidators[fieldName] = metadata;
+ }
+ }
+ return metadata;
+ }
+
+ public bool RenderedField(string fieldName)
+ {
+ bool result;
+ _renderedFields.TryGetValue(fieldName, out result);
+ return result;
+ }
+
+ public void RenderedField(string fieldName, bool value)
+ {
+ _renderedFields[fieldName] = value;
+ }
+ }
+}
diff --git a/src/System.Web.Mvc/FormMethod.cs b/src/System.Web.Mvc/FormMethod.cs
new file mode 100644
index 00000000..5e49bb3b
--- /dev/null
+++ b/src/System.Web.Mvc/FormMethod.cs
@@ -0,0 +1,8 @@
+namespace System.Web.Mvc
+{
+ public enum FormMethod
+ {
+ Get,
+ Post
+ }
+}
diff --git a/src/System.Web.Mvc/FormValueProvider.cs b/src/System.Web.Mvc/FormValueProvider.cs
new file mode 100644
index 00000000..5b9bf1e3
--- /dev/null
+++ b/src/System.Web.Mvc/FormValueProvider.cs
@@ -0,0 +1,19 @@
+using System.Globalization;
+using System.Web.Helpers;
+
+namespace System.Web.Mvc
+{
+ public sealed class FormValueProvider : NameValueCollectionValueProvider
+ {
+ public FormValueProvider(ControllerContext controllerContext)
+ : this(controllerContext, new UnvalidatedRequestValuesWrapper(controllerContext.HttpContext.Request.Unvalidated()))
+ {
+ }
+
+ // For unit testing
+ internal FormValueProvider(ControllerContext controllerContext, IUnvalidatedRequestValues unvalidatedValues)
+ : base(controllerContext.HttpContext.Request.Form, unvalidatedValues.Form, CultureInfo.CurrentCulture)
+ {
+ }
+ }
+}
diff --git a/src/System.Web.Mvc/FormValueProviderFactory.cs b/src/System.Web.Mvc/FormValueProviderFactory.cs
new file mode 100644
index 00000000..d6b28452
--- /dev/null
+++ b/src/System.Web.Mvc/FormValueProviderFactory.cs
@@ -0,0 +1,30 @@
+using System.Web.Helpers;
+
+namespace System.Web.Mvc
+{
+ public sealed class FormValueProviderFactory : ValueProviderFactory
+ {
+ private readonly UnvalidatedRequestValuesAccessor _unvalidatedValuesAccessor;
+
+ public FormValueProviderFactory()
+ : this(null)
+ {
+ }
+
+ // For unit testing
+ internal FormValueProviderFactory(UnvalidatedRequestValuesAccessor unvalidatedValuesAccessor)
+ {
+ _unvalidatedValuesAccessor = unvalidatedValuesAccessor ?? (cc => new UnvalidatedRequestValuesWrapper(cc.HttpContext.Request.Unvalidated()));
+ }
+
+ public override IValueProvider GetValueProvider(ControllerContext controllerContext)
+ {
+ if (controllerContext == null)
+ {
+ throw new ArgumentNullException("controllerContext");
+ }
+
+ return new FormValueProvider(controllerContext, _unvalidatedValuesAccessor(controllerContext));
+ }
+ }
+}
diff --git a/src/System.Web.Mvc/GlobalFilterCollection.cs b/src/System.Web.Mvc/GlobalFilterCollection.cs
new file mode 100644
index 00000000..8dfee88f
--- /dev/null
+++ b/src/System.Web.Mvc/GlobalFilterCollection.cs
@@ -0,0 +1,61 @@
+using System.Collections;
+using System.Collections.Generic;
+using System.Linq;
+
+namespace System.Web.Mvc
+{
+ public sealed class GlobalFilterCollection : IEnumerable<Filter>, IFilterProvider
+ {
+ private List<Filter> _filters = new List<Filter>();
+
+ public int Count
+ {
+ get { return _filters.Count; }
+ }
+
+ public void Add(object filter)
+ {
+ AddInternal(filter, order: null);
+ }
+
+ public void Add(object filter, int order)
+ {
+ AddInternal(filter, order);
+ }
+
+ private void AddInternal(object filter, int? order)
+ {
+ _filters.Add(new Filter(filter, FilterScope.Global, order));
+ }
+
+ public void Clear()
+ {
+ _filters.Clear();
+ }
+
+ public bool Contains(object filter)
+ {
+ return _filters.Any(f => f.Instance == filter);
+ }
+
+ public IEnumerator<Filter> GetEnumerator()
+ {
+ return _filters.GetEnumerator();
+ }
+
+ IEnumerator IEnumerable.GetEnumerator()
+ {
+ return _filters.GetEnumerator();
+ }
+
+ IEnumerable<Filter> IFilterProvider.GetFilters(ControllerContext controllerContext, ActionDescriptor actionDescriptor)
+ {
+ return this;
+ }
+
+ public void Remove(object filter)
+ {
+ _filters.RemoveAll(f => f.Instance == filter);
+ }
+ }
+}
diff --git a/src/System.Web.Mvc/GlobalFilters.cs b/src/System.Web.Mvc/GlobalFilters.cs
new file mode 100644
index 00000000..926e4fcd
--- /dev/null
+++ b/src/System.Web.Mvc/GlobalFilters.cs
@@ -0,0 +1,12 @@
+namespace System.Web.Mvc
+{
+ public static class GlobalFilters
+ {
+ static GlobalFilters()
+ {
+ Filters = new GlobalFilterCollection();
+ }
+
+ public static GlobalFilterCollection Filters { get; private set; }
+ }
+}
diff --git a/src/System.Web.Mvc/GlobalSuppressions.cs b/src/System.Web.Mvc/GlobalSuppressions.cs
new file mode 100644
index 00000000..f7a121ee
--- /dev/null
+++ b/src/System.Web.Mvc/GlobalSuppressions.cs
@@ -0,0 +1,19 @@
+// This file is used by Code Analysis to maintain SuppressMessage
+// attributes that are applied to this project.
+// Project-level suppressions either have no target or are given
+// a specific target and scoped to a namespace, type, member, etc.
+//
+// To add a suppression to this file, right-click the message in the
+// Error List, point to "Suppress Message(s)", and click
+// "In Project Suppression File".
+// You do not need to add suppressions to this file manually.
+
+using System.Diagnostics.CodeAnalysis;
+
+[assembly: SuppressMessage("Microsoft.Design", "CA2210:AssembliesShouldHaveValidStrongNames", Justification = "Assembly is delay-signed.")]
+[assembly: SuppressMessage("Microsoft.Design", "CA1020:AvoidNamespacesWithFewTypes", Scope = "namespace", Target = "System.Web.Mvc.Ajax", Justification = "Helpers reside within a separate namespace to support alternate helper classes.")]
+[assembly: SuppressMessage("Microsoft.Design", "CA1033:InterfaceMethodsShouldBeCallableByChildTypes", Scope = "member", Target = "System.Web.Mvc.TempDataDictionary.#System.Collections.Generic.ICollection`1<System.Collections.Generic.KeyValuePair`2<System.String,System.Object>>.Contains(System.Collections.Generic.KeyValuePair`2<System.String,System.Object>)", Justification = "There are no defined scenarios for wanting to derive from this class, but we don't want to prevent it either.")]
+[assembly: SuppressMessage("Microsoft.Design", "CA1033:InterfaceMethodsShouldBeCallableByChildTypes", Scope = "member", Target = "System.Web.Mvc.TempDataDictionary.#System.Collections.Generic.ICollection`1<System.Collections.Generic.KeyValuePair`2<System.String,System.Object>>.CopyTo(System.Collections.Generic.KeyValuePair`2<System.String,System.Object>[],System.Int32)", Justification = "There are no defined scenarios for wanting to derive from this class, but we don't want to prevent it either.")]
+[assembly: SuppressMessage("Microsoft.Design", "CA1033:InterfaceMethodsShouldBeCallableByChildTypes", Scope = "member", Target = "System.Web.Mvc.TempDataDictionary.#System.Collections.Generic.ICollection`1<System.Collections.Generic.KeyValuePair`2<System.String,System.Object>>.IsReadOnly", Justification = "There are no defined scenarios for wanting to derive from this class, but we don't want to prevent it either.")]
+[assembly: SuppressMessage("Microsoft.Naming", "CA1703:ResourceStringsShouldBeSpelledCorrectly", MessageId = "Param", Scope = "resource", Target = "System.Web.Mvc.Properties.MvcResources.resources", Justification = "This is the name that matches ASP.NET")]
+[assembly: SuppressMessage("Microsoft.Design", "CA1020:AvoidNamespacesWithFewTypes", Scope = "namespace", Target = "System.Web.Mvc.Razor", Justification = "This is a grouping of functionally similar components, thus a namespace is a valid way to group them.")]
diff --git a/src/System.Web.Mvc/HandleErrorAttribute.cs b/src/System.Web.Mvc/HandleErrorAttribute.cs
new file mode 100644
index 00000000..c4b61341
--- /dev/null
+++ b/src/System.Web.Mvc/HandleErrorAttribute.cs
@@ -0,0 +1,107 @@
+using System.Diagnostics.CodeAnalysis;
+using System.Globalization;
+using System.Web.Mvc.Properties;
+
+namespace System.Web.Mvc
+{
+ [SuppressMessage("Microsoft.Performance", "CA1813:AvoidUnsealedAttributes", Justification = "This attribute is AllowMultiple = true and users might want to override behavior.")]
+ [AttributeUsage(AttributeTargets.Class | AttributeTargets.Method, Inherited = true, AllowMultiple = true)]
+ public class HandleErrorAttribute : FilterAttribute, IExceptionFilter
+ {
+ private const string DefaultView = "Error";
+
+ private readonly object _typeId = new object();
+
+ private Type _exceptionType = typeof(Exception);
+ private string _master;
+ private string _view;
+
+ public Type ExceptionType
+ {
+ get { return _exceptionType; }
+ set
+ {
+ if (value == null)
+ {
+ throw new ArgumentNullException("value");
+ }
+ if (!typeof(Exception).IsAssignableFrom(value))
+ {
+ throw new ArgumentException(String.Format(CultureInfo.CurrentCulture,
+ MvcResources.ExceptionViewAttribute_NonExceptionType, value.FullName));
+ }
+
+ _exceptionType = value;
+ }
+ }
+
+ public string Master
+ {
+ get { return _master ?? String.Empty; }
+ set { _master = value; }
+ }
+
+ public override object TypeId
+ {
+ get { return _typeId; }
+ }
+
+ public string View
+ {
+ get { return (!String.IsNullOrEmpty(_view)) ? _view : DefaultView; }
+ set { _view = value; }
+ }
+
+ public virtual void OnException(ExceptionContext filterContext)
+ {
+ if (filterContext == null)
+ {
+ throw new ArgumentNullException("filterContext");
+ }
+ if (filterContext.IsChildAction)
+ {
+ return;
+ }
+
+ // If custom errors are disabled, we need to let the normal ASP.NET exception handler
+ // execute so that the user can see useful debugging information.
+ if (filterContext.ExceptionHandled || !filterContext.HttpContext.IsCustomErrorEnabled)
+ {
+ return;
+ }
+
+ Exception exception = filterContext.Exception;
+
+ // If this is not an HTTP 500 (for example, if somebody throws an HTTP 404 from an action method),
+ // ignore it.
+ if (new HttpException(null, exception).GetHttpCode() != 500)
+ {
+ return;
+ }
+
+ if (!ExceptionType.IsInstanceOfType(exception))
+ {
+ return;
+ }
+
+ string controllerName = (string)filterContext.RouteData.Values["controller"];
+ string actionName = (string)filterContext.RouteData.Values["action"];
+ HandleErrorInfo model = new HandleErrorInfo(filterContext.Exception, controllerName, actionName);
+ filterContext.Result = new ViewResult
+ {
+ ViewName = View,
+ MasterName = Master,
+ ViewData = new ViewDataDictionary<HandleErrorInfo>(model),
+ TempData = filterContext.Controller.TempData
+ };
+ filterContext.ExceptionHandled = true;
+ filterContext.HttpContext.Response.Clear();
+ filterContext.HttpContext.Response.StatusCode = 500;
+
+ // Certain versions of IIS will sometimes use their own error page when
+ // they detect a server error. Setting this property indicates that we
+ // want it to try to render ASP.NET MVC's error page instead.
+ filterContext.HttpContext.Response.TrySkipIisCustomErrors = true;
+ }
+ }
+}
diff --git a/src/System.Web.Mvc/HandleErrorInfo.cs b/src/System.Web.Mvc/HandleErrorInfo.cs
new file mode 100644
index 00000000..7b708c60
--- /dev/null
+++ b/src/System.Web.Mvc/HandleErrorInfo.cs
@@ -0,0 +1,33 @@
+using System.Web.Mvc.Properties;
+
+namespace System.Web.Mvc
+{
+ public class HandleErrorInfo
+ {
+ public HandleErrorInfo(Exception exception, string controllerName, string actionName)
+ {
+ if (exception == null)
+ {
+ throw new ArgumentNullException("exception");
+ }
+ if (String.IsNullOrEmpty(controllerName))
+ {
+ throw new ArgumentException(MvcResources.Common_NullOrEmpty, "controllerName");
+ }
+ if (String.IsNullOrEmpty(actionName))
+ {
+ throw new ArgumentException(MvcResources.Common_NullOrEmpty, "actionName");
+ }
+
+ Exception = exception;
+ ControllerName = controllerName;
+ ActionName = actionName;
+ }
+
+ public string ActionName { get; private set; }
+
+ public string ControllerName { get; private set; }
+
+ public Exception Exception { get; private set; }
+ }
+}
diff --git a/src/System.Web.Mvc/HiddenInputAttribute.cs b/src/System.Web.Mvc/HiddenInputAttribute.cs
new file mode 100644
index 00000000..1692a6b0
--- /dev/null
+++ b/src/System.Web.Mvc/HiddenInputAttribute.cs
@@ -0,0 +1,13 @@
+namespace System.Web.Mvc
+{
+ [AttributeUsage(AttributeTargets.Class | AttributeTargets.Property, AllowMultiple = false, Inherited = true)]
+ public sealed class HiddenInputAttribute : Attribute
+ {
+ public HiddenInputAttribute()
+ {
+ DisplayValue = true;
+ }
+
+ public bool DisplayValue { get; set; }
+ }
+}
diff --git a/src/System.Web.Mvc/Html/ChildActionExtensions.cs b/src/System.Web.Mvc/Html/ChildActionExtensions.cs
new file mode 100644
index 00000000..4c74a763
--- /dev/null
+++ b/src/System.Web.Mvc/Html/ChildActionExtensions.cs
@@ -0,0 +1,181 @@
+using System.Collections.Generic;
+using System.Globalization;
+using System.IO;
+using System.Linq;
+using System.Web.Mvc.Properties;
+using System.Web.Routing;
+
+namespace System.Web.Mvc.Html
+{
+ public static class ChildActionExtensions
+ {
+ // Action
+
+ public static MvcHtmlString Action(this HtmlHelper htmlHelper, string actionName)
+ {
+ return Action(htmlHelper, actionName, null /* controllerName */, null /* routeValues */);
+ }
+
+ public static MvcHtmlString Action(this HtmlHelper htmlHelper, string actionName, object routeValues)
+ {
+ return Action(htmlHelper, actionName, null /* controllerName */, new RouteValueDictionary(routeValues));
+ }
+
+ public static MvcHtmlString Action(this HtmlHelper htmlHelper, string actionName, RouteValueDictionary routeValues)
+ {
+ return Action(htmlHelper, actionName, null /* controllerName */, routeValues);
+ }
+
+ public static MvcHtmlString Action(this HtmlHelper htmlHelper, string actionName, string controllerName)
+ {
+ return Action(htmlHelper, actionName, controllerName, null /* routeValues */);
+ }
+
+ public static MvcHtmlString Action(this HtmlHelper htmlHelper, string actionName, string controllerName, object routeValues)
+ {
+ return Action(htmlHelper, actionName, controllerName, new RouteValueDictionary(routeValues));
+ }
+
+ public static MvcHtmlString Action(this HtmlHelper htmlHelper, string actionName, string controllerName, RouteValueDictionary routeValues)
+ {
+ using (StringWriter writer = new StringWriter(CultureInfo.CurrentCulture))
+ {
+ ActionHelper(htmlHelper, actionName, controllerName, routeValues, writer);
+ return MvcHtmlString.Create(writer.ToString());
+ }
+ }
+
+ // RenderAction
+
+ public static void RenderAction(this HtmlHelper htmlHelper, string actionName)
+ {
+ RenderAction(htmlHelper, actionName, null /* controllerName */, null /* routeValues */);
+ }
+
+ public static void RenderAction(this HtmlHelper htmlHelper, string actionName, object routeValues)
+ {
+ RenderAction(htmlHelper, actionName, null /* controllerName */, new RouteValueDictionary(routeValues));
+ }
+
+ public static void RenderAction(this HtmlHelper htmlHelper, string actionName, RouteValueDictionary routeValues)
+ {
+ RenderAction(htmlHelper, actionName, null /* controllerName */, routeValues);
+ }
+
+ public static void RenderAction(this HtmlHelper htmlHelper, string actionName, string controllerName)
+ {
+ RenderAction(htmlHelper, actionName, controllerName, null /* routeValues */);
+ }
+
+ public static void RenderAction(this HtmlHelper htmlHelper, string actionName, string controllerName, object routeValues)
+ {
+ RenderAction(htmlHelper, actionName, controllerName, new RouteValueDictionary(routeValues));
+ }
+
+ public static void RenderAction(this HtmlHelper htmlHelper, string actionName, string controllerName, RouteValueDictionary routeValues)
+ {
+ ActionHelper(htmlHelper, actionName, controllerName, routeValues, htmlHelper.ViewContext.Writer);
+ }
+
+ // Helpers
+
+ internal static void ActionHelper(HtmlHelper htmlHelper, string actionName, string controllerName, RouteValueDictionary routeValues, TextWriter textWriter)
+ {
+ if (htmlHelper == null)
+ {
+ throw new ArgumentNullException("htmlHelper");
+ }
+ if (String.IsNullOrEmpty(actionName))
+ {
+ throw new ArgumentException(MvcResources.Common_NullOrEmpty, "actionName");
+ }
+
+ RouteValueDictionary additionalRouteValues = routeValues;
+ routeValues = MergeDictionaries(routeValues, htmlHelper.ViewContext.RouteData.Values);
+
+ routeValues["action"] = actionName;
+ if (!String.IsNullOrEmpty(controllerName))
+ {
+ routeValues["controller"] = controllerName;
+ }
+
+ bool usingAreas;
+ VirtualPathData vpd = htmlHelper.RouteCollection.GetVirtualPathForArea(htmlHelper.ViewContext.RequestContext, null /* name */, routeValues, out usingAreas);
+ if (vpd == null)
+ {
+ throw new InvalidOperationException(MvcResources.Common_NoRouteMatched);
+ }
+
+ if (usingAreas)
+ {
+ routeValues.Remove("area");
+ if (additionalRouteValues != null)
+ {
+ additionalRouteValues.Remove("area");
+ }
+ }
+
+ if (additionalRouteValues != null)
+ {
+ routeValues[ChildActionValueProvider.ChildActionValuesKey] = new DictionaryValueProvider<object>(additionalRouteValues, CultureInfo.InvariantCulture);
+ }
+
+ RouteData routeData = CreateRouteData(vpd.Route, routeValues, vpd.DataTokens, htmlHelper.ViewContext);
+ HttpContextBase httpContext = htmlHelper.ViewContext.HttpContext;
+ RequestContext requestContext = new RequestContext(httpContext, routeData);
+ ChildActionMvcHandler handler = new ChildActionMvcHandler(requestContext);
+ httpContext.Server.Execute(HttpHandlerUtil.WrapForServerExecute(handler), textWriter, true /* preserveForm */);
+ }
+
+ private static RouteData CreateRouteData(RouteBase route, RouteValueDictionary routeValues, RouteValueDictionary dataTokens, ViewContext parentViewContext)
+ {
+ RouteData routeData = new RouteData();
+
+ foreach (KeyValuePair<string, object> kvp in routeValues)
+ {
+ routeData.Values.Add(kvp.Key, kvp.Value);
+ }
+
+ foreach (KeyValuePair<string, object> kvp in dataTokens)
+ {
+ routeData.DataTokens.Add(kvp.Key, kvp.Value);
+ }
+
+ routeData.Route = route;
+ routeData.DataTokens[ControllerContext.ParentActionViewContextToken] = parentViewContext;
+ return routeData;
+ }
+
+ private static RouteValueDictionary MergeDictionaries(params RouteValueDictionary[] dictionaries)
+ {
+ // Merge existing route values with the user provided values
+ var result = new RouteValueDictionary();
+
+ foreach (RouteValueDictionary dictionary in dictionaries.Where(d => d != null))
+ {
+ foreach (KeyValuePair<string, object> kvp in dictionary)
+ {
+ if (!result.ContainsKey(kvp.Key))
+ {
+ result.Add(kvp.Key, kvp.Value);
+ }
+ }
+ }
+
+ return result;
+ }
+
+ internal class ChildActionMvcHandler : MvcHandler
+ {
+ public ChildActionMvcHandler(RequestContext context)
+ : base(context)
+ {
+ }
+
+ protected internal override void AddVersionHeader(HttpContextBase httpContext)
+ {
+ // No version header for child actions
+ }
+ }
+ }
+}
diff --git a/src/System.Web.Mvc/Html/DefaultDisplayTemplates.cs b/src/System.Web.Mvc/Html/DefaultDisplayTemplates.cs
new file mode 100644
index 00000000..cd329237
--- /dev/null
+++ b/src/System.Web.Mvc/Html/DefaultDisplayTemplates.cs
@@ -0,0 +1,225 @@
+using System.Collections;
+using System.Collections.Generic;
+using System.Data;
+using System.Globalization;
+using System.Linq;
+using System.Text;
+using System.Web.Mvc.Properties;
+using System.Web.UI.WebControls;
+
+namespace System.Web.Mvc.Html
+{
+ internal static class DefaultDisplayTemplates
+ {
+ internal static string BooleanTemplate(HtmlHelper html)
+ {
+ bool? value = null;
+ if (html.ViewContext.ViewData.Model != null)
+ {
+ value = Convert.ToBoolean(html.ViewContext.ViewData.Model, CultureInfo.InvariantCulture);
+ }
+
+ return html.ViewContext.ViewData.ModelMetadata.IsNullableValueType
+ ? BooleanTemplateDropDownList(value)
+ : BooleanTemplateCheckbox(value ?? false);
+ }
+
+ private static string BooleanTemplateCheckbox(bool value)
+ {
+ TagBuilder inputTag = new TagBuilder("input");
+ inputTag.AddCssClass("check-box");
+ inputTag.Attributes["disabled"] = "disabled";
+ inputTag.Attributes["type"] = "checkbox";
+ if (value)
+ {
+ inputTag.Attributes["checked"] = "checked";
+ }
+
+ return inputTag.ToString(TagRenderMode.SelfClosing);
+ }
+
+ private static string BooleanTemplateDropDownList(bool? value)
+ {
+ StringBuilder builder = new StringBuilder();
+
+ TagBuilder selectTag = new TagBuilder("select");
+ selectTag.AddCssClass("list-box");
+ selectTag.AddCssClass("tri-state");
+ selectTag.Attributes["disabled"] = "disabled";
+ builder.Append(selectTag.ToString(TagRenderMode.StartTag));
+
+ foreach (SelectListItem item in DefaultEditorTemplates.TriStateValues(value))
+ {
+ builder.Append(SelectExtensions.ListItemToOption(item));
+ }
+
+ builder.Append(selectTag.ToString(TagRenderMode.EndTag));
+ return builder.ToString();
+ }
+
+ internal static string CollectionTemplate(HtmlHelper html)
+ {
+ return CollectionTemplate(html, TemplateHelpers.TemplateHelper);
+ }
+
+ internal static string CollectionTemplate(HtmlHelper html, TemplateHelpers.TemplateHelperDelegate templateHelper)
+ {
+ object model = html.ViewContext.ViewData.ModelMetadata.Model;
+ if (model == null)
+ {
+ return String.Empty;
+ }
+
+ IEnumerable collection = model as IEnumerable;
+ if (collection == null)
+ {
+ throw new InvalidOperationException(
+ String.Format(
+ CultureInfo.CurrentCulture,
+ MvcResources.Templates_TypeMustImplementIEnumerable,
+ model.GetType().FullName));
+ }
+
+ Type typeInCollection = typeof(string);
+ Type genericEnumerableType = TypeHelpers.ExtractGenericInterface(collection.GetType(), typeof(IEnumerable<>));
+ if (genericEnumerableType != null)
+ {
+ typeInCollection = genericEnumerableType.GetGenericArguments()[0];
+ }
+ bool typeInCollectionIsNullableValueType = TypeHelpers.IsNullableValueType(typeInCollection);
+
+ string oldPrefix = html.ViewContext.ViewData.TemplateInfo.HtmlFieldPrefix;
+
+ try
+ {
+ html.ViewContext.ViewData.TemplateInfo.HtmlFieldPrefix = String.Empty;
+
+ string fieldNameBase = oldPrefix;
+ StringBuilder result = new StringBuilder();
+ int index = 0;
+
+ foreach (object item in collection)
+ {
+ Type itemType = typeInCollection;
+ if (item != null && !typeInCollectionIsNullableValueType)
+ {
+ itemType = item.GetType();
+ }
+ ModelMetadata metadata = ModelMetadataProviders.Current.GetMetadataForType(() => item, itemType);
+ string fieldName = String.Format(CultureInfo.InvariantCulture, "{0}[{1}]", fieldNameBase, index++);
+ string output = templateHelper(html, metadata, fieldName, null /* templateName */, DataBoundControlMode.ReadOnly, null /* additionalViewData */);
+ result.Append(output);
+ }
+
+ return result.ToString();
+ }
+ finally
+ {
+ html.ViewContext.ViewData.TemplateInfo.HtmlFieldPrefix = oldPrefix;
+ }
+ }
+
+ internal static string DecimalTemplate(HtmlHelper html)
+ {
+ if (html.ViewContext.ViewData.TemplateInfo.FormattedModelValue == html.ViewContext.ViewData.ModelMetadata.Model)
+ {
+ html.ViewContext.ViewData.TemplateInfo.FormattedModelValue = String.Format(CultureInfo.CurrentCulture, "{0:0.00}", html.ViewContext.ViewData.ModelMetadata.Model);
+ }
+
+ return StringTemplate(html);
+ }
+
+ internal static string EmailAddressTemplate(HtmlHelper html)
+ {
+ return String.Format(CultureInfo.InvariantCulture,
+ "<a href=\"mailto:{0}\">{1}</a>",
+ html.AttributeEncode(html.ViewContext.ViewData.Model),
+ html.Encode(html.ViewContext.ViewData.TemplateInfo.FormattedModelValue));
+ }
+
+ internal static string HiddenInputTemplate(HtmlHelper html)
+ {
+ if (html.ViewContext.ViewData.ModelMetadata.HideSurroundingHtml)
+ {
+ return String.Empty;
+ }
+ return StringTemplate(html);
+ }
+
+ internal static string HtmlTemplate(HtmlHelper html)
+ {
+ return html.ViewContext.ViewData.TemplateInfo.FormattedModelValue.ToString();
+ }
+
+ internal static string ObjectTemplate(HtmlHelper html)
+ {
+ return ObjectTemplate(html, TemplateHelpers.TemplateHelper);
+ }
+
+ internal static string ObjectTemplate(HtmlHelper html, TemplateHelpers.TemplateHelperDelegate templateHelper)
+ {
+ ViewDataDictionary viewData = html.ViewContext.ViewData;
+ TemplateInfo templateInfo = viewData.TemplateInfo;
+ ModelMetadata modelMetadata = viewData.ModelMetadata;
+ StringBuilder builder = new StringBuilder();
+
+ if (modelMetadata.Model == null)
+ {
+ // DDB #225237
+ return modelMetadata.NullDisplayText;
+ }
+
+ if (templateInfo.TemplateDepth > 1)
+ {
+ // DDB #224751
+ return modelMetadata.SimpleDisplayText;
+ }
+
+ foreach (ModelMetadata propertyMetadata in modelMetadata.Properties.Where(pm => ShouldShow(pm, templateInfo)))
+ {
+ if (!propertyMetadata.HideSurroundingHtml)
+ {
+ string label = propertyMetadata.GetDisplayName();
+ if (!String.IsNullOrEmpty(label))
+ {
+ builder.AppendFormat(CultureInfo.InvariantCulture, "<div class=\"display-label\">{0}</div>", label);
+ builder.AppendLine();
+ }
+
+ builder.Append("<div class=\"display-field\">");
+ }
+
+ builder.Append(templateHelper(html, propertyMetadata, propertyMetadata.PropertyName, null /* templateName */, DataBoundControlMode.ReadOnly, null /* additionalViewData */));
+
+ if (!propertyMetadata.HideSurroundingHtml)
+ {
+ builder.AppendLine("</div>");
+ }
+ }
+
+ return builder.ToString();
+ }
+
+ private static bool ShouldShow(ModelMetadata metadata, TemplateInfo templateInfo)
+ {
+ return
+ metadata.ShowForDisplay
+ && metadata.ModelType != typeof(EntityState)
+ && !metadata.IsComplexType
+ && !templateInfo.Visited(metadata);
+ }
+
+ internal static string StringTemplate(HtmlHelper html)
+ {
+ return html.Encode(html.ViewContext.ViewData.TemplateInfo.FormattedModelValue);
+ }
+
+ internal static string UrlTemplate(HtmlHelper html)
+ {
+ return String.Format(CultureInfo.InvariantCulture,
+ "<a href=\"{0}\">{1}</a>",
+ html.AttributeEncode(html.ViewContext.ViewData.Model),
+ html.Encode(html.ViewContext.ViewData.TemplateInfo.FormattedModelValue));
+ }
+ }
+}
diff --git a/src/System.Web.Mvc/Html/DefaultEditorTemplates.cs b/src/System.Web.Mvc/Html/DefaultEditorTemplates.cs
new file mode 100644
index 00000000..c19b9986
--- /dev/null
+++ b/src/System.Web.Mvc/Html/DefaultEditorTemplates.cs
@@ -0,0 +1,236 @@
+using System.Collections;
+using System.Collections.Generic;
+using System.Data;
+using System.Data.Linq;
+using System.Globalization;
+using System.Linq;
+using System.Text;
+using System.Web.Mvc.Properties;
+using System.Web.UI.WebControls;
+
+namespace System.Web.Mvc.Html
+{
+ internal static class DefaultEditorTemplates
+ {
+ internal static string BooleanTemplate(HtmlHelper html)
+ {
+ bool? value = null;
+ if (html.ViewContext.ViewData.Model != null)
+ {
+ value = Convert.ToBoolean(html.ViewContext.ViewData.Model, CultureInfo.InvariantCulture);
+ }
+
+ return html.ViewContext.ViewData.ModelMetadata.IsNullableValueType
+ ? BooleanTemplateDropDownList(html, value)
+ : BooleanTemplateCheckbox(html, value ?? false);
+ }
+
+ private static string BooleanTemplateCheckbox(HtmlHelper html, bool value)
+ {
+ return html.CheckBox(String.Empty, value, CreateHtmlAttributes("check-box")).ToHtmlString();
+ }
+
+ private static string BooleanTemplateDropDownList(HtmlHelper html, bool? value)
+ {
+ return html.DropDownList(String.Empty, TriStateValues(value), CreateHtmlAttributes("list-box tri-state")).ToHtmlString();
+ }
+
+ internal static string CollectionTemplate(HtmlHelper html)
+ {
+ return CollectionTemplate(html, TemplateHelpers.TemplateHelper);
+ }
+
+ internal static string CollectionTemplate(HtmlHelper html, TemplateHelpers.TemplateHelperDelegate templateHelper)
+ {
+ object model = html.ViewContext.ViewData.ModelMetadata.Model;
+ if (model == null)
+ {
+ return String.Empty;
+ }
+
+ IEnumerable collection = model as IEnumerable;
+ if (collection == null)
+ {
+ throw new InvalidOperationException(
+ String.Format(
+ CultureInfo.CurrentCulture,
+ MvcResources.Templates_TypeMustImplementIEnumerable,
+ model.GetType().FullName));
+ }
+
+ Type typeInCollection = typeof(string);
+ Type genericEnumerableType = TypeHelpers.ExtractGenericInterface(collection.GetType(), typeof(IEnumerable<>));
+ if (genericEnumerableType != null)
+ {
+ typeInCollection = genericEnumerableType.GetGenericArguments()[0];
+ }
+ bool typeInCollectionIsNullableValueType = TypeHelpers.IsNullableValueType(typeInCollection);
+
+ string oldPrefix = html.ViewContext.ViewData.TemplateInfo.HtmlFieldPrefix;
+
+ try
+ {
+ html.ViewContext.ViewData.TemplateInfo.HtmlFieldPrefix = String.Empty;
+
+ string fieldNameBase = oldPrefix;
+ StringBuilder result = new StringBuilder();
+ int index = 0;
+
+ foreach (object item in collection)
+ {
+ Type itemType = typeInCollection;
+ if (item != null && !typeInCollectionIsNullableValueType)
+ {
+ itemType = item.GetType();
+ }
+ ModelMetadata metadata = ModelMetadataProviders.Current.GetMetadataForType(() => item, itemType);
+ string fieldName = String.Format(CultureInfo.InvariantCulture, "{0}[{1}]", fieldNameBase, index++);
+ string output = templateHelper(html, metadata, fieldName, null /* templateName */, DataBoundControlMode.Edit, null /* additionalViewData */);
+ result.Append(output);
+ }
+
+ return result.ToString();
+ }
+ finally
+ {
+ html.ViewContext.ViewData.TemplateInfo.HtmlFieldPrefix = oldPrefix;
+ }
+ }
+
+ internal static string DecimalTemplate(HtmlHelper html)
+ {
+ if (html.ViewContext.ViewData.TemplateInfo.FormattedModelValue == html.ViewContext.ViewData.ModelMetadata.Model)
+ {
+ html.ViewContext.ViewData.TemplateInfo.FormattedModelValue = String.Format(CultureInfo.CurrentCulture, "{0:0.00}", html.ViewContext.ViewData.ModelMetadata.Model);
+ }
+
+ return StringTemplate(html);
+ }
+
+ internal static string HiddenInputTemplate(HtmlHelper html)
+ {
+ string result;
+
+ if (html.ViewContext.ViewData.ModelMetadata.HideSurroundingHtml)
+ {
+ result = String.Empty;
+ }
+ else
+ {
+ result = DefaultDisplayTemplates.StringTemplate(html);
+ }
+
+ object model = html.ViewContext.ViewData.Model;
+
+ Binary modelAsBinary = model as Binary;
+ if (modelAsBinary != null)
+ {
+ model = Convert.ToBase64String(modelAsBinary.ToArray());
+ }
+ else
+ {
+ byte[] modelAsByteArray = model as byte[];
+ if (modelAsByteArray != null)
+ {
+ model = Convert.ToBase64String(modelAsByteArray);
+ }
+ }
+
+ result += html.Hidden(String.Empty, model).ToHtmlString();
+ return result;
+ }
+
+ internal static string MultilineTextTemplate(HtmlHelper html)
+ {
+ return html.TextArea(String.Empty,
+ html.ViewContext.ViewData.TemplateInfo.FormattedModelValue.ToString(),
+ 0 /* rows */, 0 /* columns */,
+ CreateHtmlAttributes("text-box multi-line")).ToHtmlString();
+ }
+
+ private static IDictionary<string, object> CreateHtmlAttributes(string className)
+ {
+ return new Dictionary<string, object>()
+ {
+ { "class", className }
+ };
+ }
+
+ internal static string ObjectTemplate(HtmlHelper html)
+ {
+ return ObjectTemplate(html, TemplateHelpers.TemplateHelper);
+ }
+
+ internal static string ObjectTemplate(HtmlHelper html, TemplateHelpers.TemplateHelperDelegate templateHelper)
+ {
+ ViewDataDictionary viewData = html.ViewContext.ViewData;
+ TemplateInfo templateInfo = viewData.TemplateInfo;
+ ModelMetadata modelMetadata = viewData.ModelMetadata;
+ StringBuilder builder = new StringBuilder();
+
+ if (templateInfo.TemplateDepth > 1)
+ {
+ // DDB #224751
+ return modelMetadata.Model == null ? modelMetadata.NullDisplayText : modelMetadata.SimpleDisplayText;
+ }
+
+ foreach (ModelMetadata propertyMetadata in modelMetadata.Properties.Where(pm => ShouldShow(pm, templateInfo)))
+ {
+ if (!propertyMetadata.HideSurroundingHtml)
+ {
+ string label = LabelExtensions.LabelHelper(html, propertyMetadata, propertyMetadata.PropertyName).ToHtmlString();
+ if (!String.IsNullOrEmpty(label))
+ {
+ builder.AppendFormat(CultureInfo.InvariantCulture, "<div class=\"editor-label\">{0}</div>\r\n", label);
+ }
+
+ builder.Append("<div class=\"editor-field\">");
+ }
+
+ builder.Append(templateHelper(html, propertyMetadata, propertyMetadata.PropertyName, null /* templateName */, DataBoundControlMode.Edit, null /* additionalViewData */));
+
+ if (!propertyMetadata.HideSurroundingHtml)
+ {
+ builder.Append(" ");
+ builder.Append(html.ValidationMessage(propertyMetadata.PropertyName));
+ builder.Append("</div>\r\n");
+ }
+ }
+
+ return builder.ToString();
+ }
+
+ internal static string PasswordTemplate(HtmlHelper html)
+ {
+ return html.Password(String.Empty,
+ html.ViewContext.ViewData.TemplateInfo.FormattedModelValue,
+ CreateHtmlAttributes("text-box single-line password")).ToHtmlString();
+ }
+
+ private static bool ShouldShow(ModelMetadata metadata, TemplateInfo templateInfo)
+ {
+ return
+ metadata.ShowForEdit
+ && metadata.ModelType != typeof(EntityState)
+ && !metadata.IsComplexType
+ && !templateInfo.Visited(metadata);
+ }
+
+ internal static string StringTemplate(HtmlHelper html)
+ {
+ return html.TextBox(String.Empty,
+ html.ViewContext.ViewData.TemplateInfo.FormattedModelValue,
+ CreateHtmlAttributes("text-box single-line")).ToHtmlString();
+ }
+
+ internal static List<SelectListItem> TriStateValues(bool? value)
+ {
+ return new List<SelectListItem>
+ {
+ new SelectListItem { Text = MvcResources.Common_TriState_NotSet, Value = String.Empty, Selected = !value.HasValue },
+ new SelectListItem { Text = MvcResources.Common_TriState_True, Value = "true", Selected = value.HasValue && value.Value },
+ new SelectListItem { Text = MvcResources.Common_TriState_False, Value = "false", Selected = value.HasValue && !value.Value },
+ };
+ }
+ }
+}
diff --git a/src/System.Web.Mvc/Html/DisplayExtensions.cs b/src/System.Web.Mvc/Html/DisplayExtensions.cs
new file mode 100644
index 00000000..4b5eaa0a
--- /dev/null
+++ b/src/System.Web.Mvc/Html/DisplayExtensions.cs
@@ -0,0 +1,105 @@
+using System.Diagnostics.CodeAnalysis;
+using System.Linq.Expressions;
+using System.Web.UI.WebControls;
+
+namespace System.Web.Mvc.Html
+{
+ public static class DisplayExtensions
+ {
+ public static MvcHtmlString Display(this HtmlHelper html, string expression)
+ {
+ return TemplateHelpers.Template(html, expression, null /* templateName */, null /* htmlFieldName */, DataBoundControlMode.ReadOnly, null /* additionalViewData */);
+ }
+
+ public static MvcHtmlString Display(this HtmlHelper html, string expression, object additionalViewData)
+ {
+ return TemplateHelpers.Template(html, expression, null /* templateName */, null /* htmlFieldName */, DataBoundControlMode.ReadOnly, additionalViewData);
+ }
+
+ public static MvcHtmlString Display(this HtmlHelper html, string expression, string templateName)
+ {
+ return TemplateHelpers.Template(html, expression, templateName, null /* htmlFieldName */, DataBoundControlMode.ReadOnly, null /* additionalViewData */);
+ }
+
+ public static MvcHtmlString Display(this HtmlHelper html, string expression, string templateName, object additionalViewData)
+ {
+ return TemplateHelpers.Template(html, expression, templateName, null /* htmlFieldName */, DataBoundControlMode.ReadOnly, additionalViewData);
+ }
+
+ public static MvcHtmlString Display(this HtmlHelper html, string expression, string templateName, string htmlFieldName)
+ {
+ return TemplateHelpers.Template(html, expression, templateName, htmlFieldName, DataBoundControlMode.ReadOnly, null /* additionalViewData */);
+ }
+
+ public static MvcHtmlString Display(this HtmlHelper html, string expression, string templateName, string htmlFieldName, object additionalViewData)
+ {
+ return TemplateHelpers.Template(html, expression, templateName, htmlFieldName, DataBoundControlMode.ReadOnly, additionalViewData);
+ }
+
+ [SuppressMessage("Microsoft.Design", "CA1006:DoNotNestGenericTypesInMemberSignatures", Justification = "This is an appropriate nesting of generic types")]
+ public static MvcHtmlString DisplayFor<TModel, TValue>(this HtmlHelper<TModel> html, Expression<Func<TModel, TValue>> expression)
+ {
+ return TemplateHelpers.TemplateFor(html, expression, null /* templateName */, null /* htmlFieldName */, DataBoundControlMode.ReadOnly, null /* additionalViewData */);
+ }
+
+ [SuppressMessage("Microsoft.Design", "CA1006:DoNotNestGenericTypesInMemberSignatures", Justification = "This is an appropriate nesting of generic types")]
+ public static MvcHtmlString DisplayFor<TModel, TValue>(this HtmlHelper<TModel> html, Expression<Func<TModel, TValue>> expression, object additionalViewData)
+ {
+ return TemplateHelpers.TemplateFor(html, expression, null /* templateName */, null /* htmlFieldName */, DataBoundControlMode.ReadOnly, additionalViewData);
+ }
+
+ [SuppressMessage("Microsoft.Design", "CA1006:DoNotNestGenericTypesInMemberSignatures", Justification = "This is an appropriate nesting of generic types")]
+ public static MvcHtmlString DisplayFor<TModel, TValue>(this HtmlHelper<TModel> html, Expression<Func<TModel, TValue>> expression, string templateName)
+ {
+ return TemplateHelpers.TemplateFor(html, expression, templateName, null /* htmlFieldName */, DataBoundControlMode.ReadOnly, null /* additionalViewData */);
+ }
+
+ [SuppressMessage("Microsoft.Design", "CA1006:DoNotNestGenericTypesInMemberSignatures", Justification = "This is an appropriate nesting of generic types")]
+ public static MvcHtmlString DisplayFor<TModel, TValue>(this HtmlHelper<TModel> html, Expression<Func<TModel, TValue>> expression, string templateName, object additionalViewData)
+ {
+ return TemplateHelpers.TemplateFor(html, expression, templateName, null /* htmlFieldName */, DataBoundControlMode.ReadOnly, additionalViewData);
+ }
+
+ [SuppressMessage("Microsoft.Design", "CA1006:DoNotNestGenericTypesInMemberSignatures", Justification = "This is an appropriate nesting of generic types")]
+ public static MvcHtmlString DisplayFor<TModel, TValue>(this HtmlHelper<TModel> html, Expression<Func<TModel, TValue>> expression, string templateName, string htmlFieldName)
+ {
+ return TemplateHelpers.TemplateFor(html, expression, templateName, htmlFieldName, DataBoundControlMode.ReadOnly, null /* additionalViewData */);
+ }
+
+ [SuppressMessage("Microsoft.Design", "CA1006:DoNotNestGenericTypesInMemberSignatures", Justification = "This is an appropriate nesting of generic types")]
+ public static MvcHtmlString DisplayFor<TModel, TValue>(this HtmlHelper<TModel> html, Expression<Func<TModel, TValue>> expression, string templateName, string htmlFieldName, object additionalViewData)
+ {
+ return TemplateHelpers.TemplateFor(html, expression, templateName, htmlFieldName, DataBoundControlMode.ReadOnly, additionalViewData);
+ }
+
+ public static MvcHtmlString DisplayForModel(this HtmlHelper html)
+ {
+ return MvcHtmlString.Create(TemplateHelpers.TemplateHelper(html, html.ViewData.ModelMetadata, String.Empty, null /* templateName */, DataBoundControlMode.ReadOnly, null /* additionalViewData */));
+ }
+
+ public static MvcHtmlString DisplayForModel(this HtmlHelper html, object additionalViewData)
+ {
+ return MvcHtmlString.Create(TemplateHelpers.TemplateHelper(html, html.ViewData.ModelMetadata, String.Empty, null /* templateName */, DataBoundControlMode.ReadOnly, additionalViewData));
+ }
+
+ public static MvcHtmlString DisplayForModel(this HtmlHelper html, string templateName)
+ {
+ return MvcHtmlString.Create(TemplateHelpers.TemplateHelper(html, html.ViewData.ModelMetadata, String.Empty, templateName, DataBoundControlMode.ReadOnly, null /* additionalViewData */));
+ }
+
+ public static MvcHtmlString DisplayForModel(this HtmlHelper html, string templateName, object additionalViewData)
+ {
+ return MvcHtmlString.Create(TemplateHelpers.TemplateHelper(html, html.ViewData.ModelMetadata, String.Empty, templateName, DataBoundControlMode.ReadOnly, additionalViewData));
+ }
+
+ public static MvcHtmlString DisplayForModel(this HtmlHelper html, string templateName, string htmlFieldName)
+ {
+ return MvcHtmlString.Create(TemplateHelpers.TemplateHelper(html, html.ViewData.ModelMetadata, htmlFieldName, templateName, DataBoundControlMode.ReadOnly, null /* additionalViewData */));
+ }
+
+ public static MvcHtmlString DisplayForModel(this HtmlHelper html, string templateName, string htmlFieldName, object additionalViewData)
+ {
+ return MvcHtmlString.Create(TemplateHelpers.TemplateHelper(html, html.ViewData.ModelMetadata, htmlFieldName, templateName, DataBoundControlMode.ReadOnly, additionalViewData));
+ }
+ }
+}
diff --git a/src/System.Web.Mvc/Html/DisplayNameExtensions.cs b/src/System.Web.Mvc/Html/DisplayNameExtensions.cs
new file mode 100644
index 00000000..b683aef2
--- /dev/null
+++ b/src/System.Web.Mvc/Html/DisplayNameExtensions.cs
@@ -0,0 +1,60 @@
+using System.Collections.Generic;
+using System.Diagnostics.CodeAnalysis;
+using System.Linq;
+using System.Linq.Expressions;
+
+namespace System.Web.Mvc.Html
+{
+ public static class DisplayNameExtensions
+ {
+ public static MvcHtmlString DisplayName(this HtmlHelper html, string expression)
+ {
+ return DisplayNameInternal(html, expression, metadataProvider: null);
+ }
+
+ internal static MvcHtmlString DisplayNameInternal(this HtmlHelper html, string expression, ModelMetadataProvider metadataProvider)
+ {
+ return DisplayNameHelper(ModelMetadata.FromStringExpression(expression, html.ViewData, metadataProvider),
+ expression);
+ }
+
+ [SuppressMessage("Microsoft.Design", "CA1006:DoNotNestGenericTypesInMemberSignatures", Justification = "This is an appropriate nesting of generic types")]
+ public static MvcHtmlString DisplayNameFor<TModel, TValue>(this HtmlHelper<IEnumerable<TModel>> html, Expression<Func<TModel, TValue>> expression)
+ {
+ return DisplayNameForInternal(html, expression, metadataProvider: null);
+ }
+
+ [SuppressMessage("Microsoft.Usage", "CA1801:ReviewUnusedParameters", Justification = "This is an extension method")]
+ internal static MvcHtmlString DisplayNameForInternal<TModel, TValue>(this HtmlHelper<IEnumerable<TModel>> html, Expression<Func<TModel, TValue>> expression, ModelMetadataProvider metadataProvider)
+ {
+ return DisplayNameHelper(ModelMetadata.FromLambdaExpression(expression, new ViewDataDictionary<TModel>(), metadataProvider),
+ ExpressionHelper.GetExpressionText(expression));
+ }
+
+ [SuppressMessage("Microsoft.Design", "CA1006:DoNotNestGenericTypesInMemberSignatures", Justification = "This is an appropriate nesting of generic types")]
+ public static MvcHtmlString DisplayNameFor<TModel, TValue>(this HtmlHelper<TModel> html, Expression<Func<TModel, TValue>> expression)
+ {
+ return DisplayNameForInternal(html, expression, metadataProvider: null);
+ }
+
+ internal static MvcHtmlString DisplayNameForInternal<TModel, TValue>(this HtmlHelper<TModel> html, Expression<Func<TModel, TValue>> expression, ModelMetadataProvider metadataProvider)
+ {
+ return DisplayNameHelper(ModelMetadata.FromLambdaExpression(expression, html.ViewData, metadataProvider),
+ ExpressionHelper.GetExpressionText(expression));
+ }
+
+ public static MvcHtmlString DisplayNameForModel(this HtmlHelper html)
+ {
+ return DisplayNameHelper(html.ViewData.ModelMetadata, String.Empty);
+ }
+
+ internal static MvcHtmlString DisplayNameHelper(ModelMetadata metadata, string htmlFieldName)
+ {
+ // We don't call ModelMetadata.GetDisplayName here because we want to fall back to the field name rather than the ModelType.
+ // This is similar to how the LabelHelpers get the text of a label.
+ string resolvedDisplayName = metadata.DisplayName ?? metadata.PropertyName ?? htmlFieldName.Split('.').Last();
+
+ return new MvcHtmlString(HttpUtility.HtmlEncode(resolvedDisplayName));
+ }
+ }
+}
diff --git a/src/System.Web.Mvc/Html/DisplayTextExtensions.cs b/src/System.Web.Mvc/Html/DisplayTextExtensions.cs
new file mode 100644
index 00000000..92142125
--- /dev/null
+++ b/src/System.Web.Mvc/Html/DisplayTextExtensions.cs
@@ -0,0 +1,24 @@
+using System.Diagnostics.CodeAnalysis;
+using System.Linq.Expressions;
+
+namespace System.Web.Mvc.Html
+{
+ public static class DisplayTextExtensions
+ {
+ public static MvcHtmlString DisplayText(this HtmlHelper html, string name)
+ {
+ return DisplayTextHelper(ModelMetadata.FromStringExpression(name, html.ViewContext.ViewData));
+ }
+
+ [SuppressMessage("Microsoft.Design", "CA1006:DoNotNestGenericTypesInMemberSignatures", Justification = "This is an appropriate nesting of generic types")]
+ public static MvcHtmlString DisplayTextFor<TModel, TResult>(this HtmlHelper<TModel> html, Expression<Func<TModel, TResult>> expression)
+ {
+ return DisplayTextHelper(ModelMetadata.FromLambdaExpression(expression, html.ViewData));
+ }
+
+ private static MvcHtmlString DisplayTextHelper(ModelMetadata metadata)
+ {
+ return MvcHtmlString.Create(metadata.SimpleDisplayText);
+ }
+ }
+}
diff --git a/src/System.Web.Mvc/Html/EditorExtensions.cs b/src/System.Web.Mvc/Html/EditorExtensions.cs
new file mode 100644
index 00000000..7caa4887
--- /dev/null
+++ b/src/System.Web.Mvc/Html/EditorExtensions.cs
@@ -0,0 +1,105 @@
+using System.Diagnostics.CodeAnalysis;
+using System.Linq.Expressions;
+using System.Web.UI.WebControls;
+
+namespace System.Web.Mvc.Html
+{
+ public static class EditorExtensions
+ {
+ public static MvcHtmlString Editor(this HtmlHelper html, string expression)
+ {
+ return TemplateHelpers.Template(html, expression, null /* templateName */, null /* htmlFieldName */, DataBoundControlMode.Edit, null /* additionalViewData */);
+ }
+
+ public static MvcHtmlString Editor(this HtmlHelper html, string expression, object additionalViewData)
+ {
+ return TemplateHelpers.Template(html, expression, null /* templateName */, null /* htmlFieldName */, DataBoundControlMode.Edit, additionalViewData);
+ }
+
+ public static MvcHtmlString Editor(this HtmlHelper html, string expression, string templateName)
+ {
+ return TemplateHelpers.Template(html, expression, templateName, null /* htmlFieldName */, DataBoundControlMode.Edit, null /* additionalViewData */);
+ }
+
+ public static MvcHtmlString Editor(this HtmlHelper html, string expression, string templateName, object additionalViewData)
+ {
+ return TemplateHelpers.Template(html, expression, templateName, null /* htmlFieldName */, DataBoundControlMode.Edit, additionalViewData);
+ }
+
+ public static MvcHtmlString Editor(this HtmlHelper html, string expression, string templateName, string htmlFieldName)
+ {
+ return TemplateHelpers.Template(html, expression, templateName, htmlFieldName, DataBoundControlMode.Edit, null /* additionalViewData */);
+ }
+
+ public static MvcHtmlString Editor(this HtmlHelper html, string expression, string templateName, string htmlFieldName, object additionalViewData)
+ {
+ return TemplateHelpers.Template(html, expression, templateName, htmlFieldName, DataBoundControlMode.Edit, additionalViewData);
+ }
+
+ [SuppressMessage("Microsoft.Design", "CA1006:DoNotNestGenericTypesInMemberSignatures", Justification = "This is an appropriate nesting of generic types")]
+ public static MvcHtmlString EditorFor<TModel, TValue>(this HtmlHelper<TModel> html, Expression<Func<TModel, TValue>> expression)
+ {
+ return TemplateHelpers.TemplateFor(html, expression, null /* templateName */, null /* htmlFieldName */, DataBoundControlMode.Edit, null /* additionalViewData */);
+ }
+
+ [SuppressMessage("Microsoft.Design", "CA1006:DoNotNestGenericTypesInMemberSignatures", Justification = "This is an appropriate nesting of generic types")]
+ public static MvcHtmlString EditorFor<TModel, TValue>(this HtmlHelper<TModel> html, Expression<Func<TModel, TValue>> expression, object additionalViewData)
+ {
+ return TemplateHelpers.TemplateFor(html, expression, null /* templateName */, null /* htmlFieldName */, DataBoundControlMode.Edit, additionalViewData);
+ }
+
+ [SuppressMessage("Microsoft.Design", "CA1006:DoNotNestGenericTypesInMemberSignatures", Justification = "This is an appropriate nesting of generic types")]
+ public static MvcHtmlString EditorFor<TModel, TValue>(this HtmlHelper<TModel> html, Expression<Func<TModel, TValue>> expression, string templateName)
+ {
+ return TemplateHelpers.TemplateFor(html, expression, templateName, null /* htmlFieldName */, DataBoundControlMode.Edit, null /* additionalViewData */);
+ }
+
+ [SuppressMessage("Microsoft.Design", "CA1006:DoNotNestGenericTypesInMemberSignatures", Justification = "This is an appropriate nesting of generic types")]
+ public static MvcHtmlString EditorFor<TModel, TValue>(this HtmlHelper<TModel> html, Expression<Func<TModel, TValue>> expression, string templateName, object additionalViewData)
+ {
+ return TemplateHelpers.TemplateFor(html, expression, templateName, null /* htmlFieldName */, DataBoundControlMode.Edit, additionalViewData);
+ }
+
+ [SuppressMessage("Microsoft.Design", "CA1006:DoNotNestGenericTypesInMemberSignatures", Justification = "This is an appropriate nesting of generic types")]
+ public static MvcHtmlString EditorFor<TModel, TValue>(this HtmlHelper<TModel> html, Expression<Func<TModel, TValue>> expression, string templateName, string htmlFieldName)
+ {
+ return TemplateHelpers.TemplateFor(html, expression, templateName, htmlFieldName, DataBoundControlMode.Edit, null /* additionalViewData */);
+ }
+
+ [SuppressMessage("Microsoft.Design", "CA1006:DoNotNestGenericTypesInMemberSignatures", Justification = "This is an appropriate nesting of generic types")]
+ public static MvcHtmlString EditorFor<TModel, TValue>(this HtmlHelper<TModel> html, Expression<Func<TModel, TValue>> expression, string templateName, string htmlFieldName, object additionalViewData)
+ {
+ return TemplateHelpers.TemplateFor(html, expression, templateName, htmlFieldName, DataBoundControlMode.Edit, additionalViewData);
+ }
+
+ public static MvcHtmlString EditorForModel(this HtmlHelper html)
+ {
+ return MvcHtmlString.Create(TemplateHelpers.TemplateHelper(html, html.ViewData.ModelMetadata, String.Empty, null /* templateName */, DataBoundControlMode.Edit, null /* additionalViewData */));
+ }
+
+ public static MvcHtmlString EditorForModel(this HtmlHelper html, object additionalViewData)
+ {
+ return MvcHtmlString.Create(TemplateHelpers.TemplateHelper(html, html.ViewData.ModelMetadata, String.Empty, null /* templateName */, DataBoundControlMode.Edit, additionalViewData));
+ }
+
+ public static MvcHtmlString EditorForModel(this HtmlHelper html, string templateName)
+ {
+ return MvcHtmlString.Create(TemplateHelpers.TemplateHelper(html, html.ViewData.ModelMetadata, String.Empty, templateName, DataBoundControlMode.Edit, null /* additionalViewData */));
+ }
+
+ public static MvcHtmlString EditorForModel(this HtmlHelper html, string templateName, object additionalViewData)
+ {
+ return MvcHtmlString.Create(TemplateHelpers.TemplateHelper(html, html.ViewData.ModelMetadata, String.Empty, templateName, DataBoundControlMode.Edit, additionalViewData));
+ }
+
+ public static MvcHtmlString EditorForModel(this HtmlHelper html, string templateName, string htmlFieldName)
+ {
+ return MvcHtmlString.Create(TemplateHelpers.TemplateHelper(html, html.ViewData.ModelMetadata, htmlFieldName, templateName, DataBoundControlMode.Edit, null /* additionalViewData */));
+ }
+
+ public static MvcHtmlString EditorForModel(this HtmlHelper html, string templateName, string htmlFieldName, object additionalViewData)
+ {
+ return MvcHtmlString.Create(TemplateHelpers.TemplateHelper(html, html.ViewData.ModelMetadata, htmlFieldName, templateName, DataBoundControlMode.Edit, additionalViewData));
+ }
+ }
+}
diff --git a/src/System.Web.Mvc/Html/FormExtensions.cs b/src/System.Web.Mvc/Html/FormExtensions.cs
new file mode 100644
index 00000000..4a6780cb
--- /dev/null
+++ b/src/System.Web.Mvc/Html/FormExtensions.cs
@@ -0,0 +1,180 @@
+using System.Collections.Generic;
+using System.Diagnostics.CodeAnalysis;
+using System.Web.Routing;
+
+namespace System.Web.Mvc.Html
+{
+ public static class FormExtensions
+ {
+ public static MvcForm BeginForm(this HtmlHelper htmlHelper)
+ {
+ // generates <form action="{current url}" method="post">...</form>
+ string formAction = htmlHelper.ViewContext.HttpContext.Request.RawUrl;
+ return FormHelper(htmlHelper, formAction, FormMethod.Post, new RouteValueDictionary());
+ }
+
+ public static MvcForm BeginForm(this HtmlHelper htmlHelper, object routeValues)
+ {
+ return BeginForm(htmlHelper, null, null, new RouteValueDictionary(routeValues), FormMethod.Post, new RouteValueDictionary());
+ }
+
+ public static MvcForm BeginForm(this HtmlHelper htmlHelper, RouteValueDictionary routeValues)
+ {
+ return BeginForm(htmlHelper, null, null, routeValues, FormMethod.Post, new RouteValueDictionary());
+ }
+
+ public static MvcForm BeginForm(this HtmlHelper htmlHelper, string actionName, string controllerName)
+ {
+ return BeginForm(htmlHelper, actionName, controllerName, new RouteValueDictionary(), FormMethod.Post, new RouteValueDictionary());
+ }
+
+ public static MvcForm BeginForm(this HtmlHelper htmlHelper, string actionName, string controllerName, object routeValues)
+ {
+ return BeginForm(htmlHelper, actionName, controllerName, new RouteValueDictionary(routeValues), FormMethod.Post, new RouteValueDictionary());
+ }
+
+ public static MvcForm BeginForm(this HtmlHelper htmlHelper, string actionName, string controllerName, RouteValueDictionary routeValues)
+ {
+ return BeginForm(htmlHelper, actionName, controllerName, routeValues, FormMethod.Post, new RouteValueDictionary());
+ }
+
+ public static MvcForm BeginForm(this HtmlHelper htmlHelper, string actionName, string controllerName, FormMethod method)
+ {
+ return BeginForm(htmlHelper, actionName, controllerName, new RouteValueDictionary(), method, new RouteValueDictionary());
+ }
+
+ public static MvcForm BeginForm(this HtmlHelper htmlHelper, string actionName, string controllerName, object routeValues, FormMethod method)
+ {
+ return BeginForm(htmlHelper, actionName, controllerName, new RouteValueDictionary(routeValues), method, new RouteValueDictionary());
+ }
+
+ public static MvcForm BeginForm(this HtmlHelper htmlHelper, string actionName, string controllerName, RouteValueDictionary routeValues, FormMethod method)
+ {
+ return BeginForm(htmlHelper, actionName, controllerName, routeValues, method, new RouteValueDictionary());
+ }
+
+ public static MvcForm BeginForm(this HtmlHelper htmlHelper, string actionName, string controllerName, FormMethod method, object htmlAttributes)
+ {
+ return BeginForm(htmlHelper, actionName, controllerName, new RouteValueDictionary(), method, HtmlHelper.AnonymousObjectToHtmlAttributes(htmlAttributes));
+ }
+
+ public static MvcForm BeginForm(this HtmlHelper htmlHelper, string actionName, string controllerName, FormMethod method, IDictionary<string, object> htmlAttributes)
+ {
+ return BeginForm(htmlHelper, actionName, controllerName, new RouteValueDictionary(), method, htmlAttributes);
+ }
+
+ public static MvcForm BeginForm(this HtmlHelper htmlHelper, string actionName, string controllerName, object routeValues, FormMethod method, object htmlAttributes)
+ {
+ return BeginForm(htmlHelper, actionName, controllerName, new RouteValueDictionary(routeValues), method, HtmlHelper.AnonymousObjectToHtmlAttributes(htmlAttributes));
+ }
+
+ public static MvcForm BeginForm(this HtmlHelper htmlHelper, string actionName, string controllerName, RouteValueDictionary routeValues, FormMethod method, IDictionary<string, object> htmlAttributes)
+ {
+ string formAction = UrlHelper.GenerateUrl(null /* routeName */, actionName, controllerName, routeValues, htmlHelper.RouteCollection, htmlHelper.ViewContext.RequestContext, true /* includeImplicitMvcValues */);
+ return FormHelper(htmlHelper, formAction, method, htmlAttributes);
+ }
+
+ public static MvcForm BeginRouteForm(this HtmlHelper htmlHelper, object routeValues)
+ {
+ return BeginRouteForm(htmlHelper, null /* routeName */, new RouteValueDictionary(routeValues), FormMethod.Post, new RouteValueDictionary());
+ }
+
+ public static MvcForm BeginRouteForm(this HtmlHelper htmlHelper, RouteValueDictionary routeValues)
+ {
+ return BeginRouteForm(htmlHelper, null /* routeName */, routeValues, FormMethod.Post, new RouteValueDictionary());
+ }
+
+ public static MvcForm BeginRouteForm(this HtmlHelper htmlHelper, string routeName)
+ {
+ return BeginRouteForm(htmlHelper, routeName, new RouteValueDictionary(), FormMethod.Post, new RouteValueDictionary());
+ }
+
+ public static MvcForm BeginRouteForm(this HtmlHelper htmlHelper, string routeName, object routeValues)
+ {
+ return BeginRouteForm(htmlHelper, routeName, new RouteValueDictionary(routeValues), FormMethod.Post, new RouteValueDictionary());
+ }
+
+ public static MvcForm BeginRouteForm(this HtmlHelper htmlHelper, string routeName, RouteValueDictionary routeValues)
+ {
+ return BeginRouteForm(htmlHelper, routeName, routeValues, FormMethod.Post, new RouteValueDictionary());
+ }
+
+ public static MvcForm BeginRouteForm(this HtmlHelper htmlHelper, string routeName, FormMethod method)
+ {
+ return BeginRouteForm(htmlHelper, routeName, new RouteValueDictionary(), method, new RouteValueDictionary());
+ }
+
+ public static MvcForm BeginRouteForm(this HtmlHelper htmlHelper, string routeName, object routeValues, FormMethod method)
+ {
+ return BeginRouteForm(htmlHelper, routeName, new RouteValueDictionary(routeValues), method, new RouteValueDictionary());
+ }
+
+ public static MvcForm BeginRouteForm(this HtmlHelper htmlHelper, string routeName, RouteValueDictionary routeValues, FormMethod method)
+ {
+ return BeginRouteForm(htmlHelper, routeName, routeValues, method, new RouteValueDictionary());
+ }
+
+ public static MvcForm BeginRouteForm(this HtmlHelper htmlHelper, string routeName, FormMethod method, object htmlAttributes)
+ {
+ return BeginRouteForm(htmlHelper, routeName, new RouteValueDictionary(), method, HtmlHelper.AnonymousObjectToHtmlAttributes(htmlAttributes));
+ }
+
+ public static MvcForm BeginRouteForm(this HtmlHelper htmlHelper, string routeName, FormMethod method, IDictionary<string, object> htmlAttributes)
+ {
+ return BeginRouteForm(htmlHelper, routeName, new RouteValueDictionary(), method, htmlAttributes);
+ }
+
+ public static MvcForm BeginRouteForm(this HtmlHelper htmlHelper, string routeName, object routeValues, FormMethod method, object htmlAttributes)
+ {
+ return BeginRouteForm(htmlHelper, routeName, new RouteValueDictionary(routeValues), method, HtmlHelper.AnonymousObjectToHtmlAttributes(htmlAttributes));
+ }
+
+ public static MvcForm BeginRouteForm(this HtmlHelper htmlHelper, string routeName, RouteValueDictionary routeValues, FormMethod method, IDictionary<string, object> htmlAttributes)
+ {
+ string formAction = UrlHelper.GenerateUrl(routeName, null, null, routeValues, htmlHelper.RouteCollection, htmlHelper.ViewContext.RequestContext, false /* includeImplicitMvcValues */);
+ return FormHelper(htmlHelper, formAction, method, htmlAttributes);
+ }
+
+ public static void EndForm(this HtmlHelper htmlHelper)
+ {
+ EndForm(htmlHelper.ViewContext);
+ }
+
+ internal static void EndForm(ViewContext viewContext)
+ {
+ viewContext.Writer.Write("</form>");
+ viewContext.OutputClientValidation();
+ viewContext.FormContext = null;
+ }
+
+ [SuppressMessage("Microsoft.Reliability", "CA2000:Dispose objects before losing scope", Justification = "Because disposing the object would write to the response stream, you don't want to prematurely dispose of this object.")]
+ private static MvcForm FormHelper(this HtmlHelper htmlHelper, string formAction, FormMethod method, IDictionary<string, object> htmlAttributes)
+ {
+ TagBuilder tagBuilder = new TagBuilder("form");
+ tagBuilder.MergeAttributes(htmlAttributes);
+ // action is implicitly generated, so htmlAttributes take precedence.
+ tagBuilder.MergeAttribute("action", formAction);
+ // method is an explicit parameter, so it takes precedence over the htmlAttributes.
+ tagBuilder.MergeAttribute("method", HtmlHelper.GetFormMethodString(method), true);
+
+ bool traditionalJavascriptEnabled = htmlHelper.ViewContext.ClientValidationEnabled
+ && !htmlHelper.ViewContext.UnobtrusiveJavaScriptEnabled;
+
+ if (traditionalJavascriptEnabled)
+ {
+ // forms must have an ID for client validation
+ tagBuilder.GenerateId(htmlHelper.ViewContext.FormIdGenerator());
+ }
+
+ htmlHelper.ViewContext.Writer.Write(tagBuilder.ToString(TagRenderMode.StartTag));
+ MvcForm theForm = new MvcForm(htmlHelper.ViewContext);
+
+ if (traditionalJavascriptEnabled)
+ {
+ htmlHelper.ViewContext.FormContext.FormId = tagBuilder.Attributes["id"];
+ }
+
+ return theForm;
+ }
+ }
+}
diff --git a/src/System.Web.Mvc/Html/InputExtensions.cs b/src/System.Web.Mvc/Html/InputExtensions.cs
new file mode 100644
index 00000000..fa3d7e9c
--- /dev/null
+++ b/src/System.Web.Mvc/Html/InputExtensions.cs
@@ -0,0 +1,581 @@
+using System.Collections.Generic;
+using System.Data.Linq;
+using System.Diagnostics.CodeAnalysis;
+using System.Globalization;
+using System.Linq.Expressions;
+using System.Text;
+using System.Web.Mvc.Properties;
+using System.Web.Routing;
+
+namespace System.Web.Mvc.Html
+{
+ public static class InputExtensions
+ {
+ // CheckBox
+
+ public static MvcHtmlString CheckBox(this HtmlHelper htmlHelper, string name)
+ {
+ return CheckBox(htmlHelper, name, htmlAttributes: (object)null);
+ }
+
+ public static MvcHtmlString CheckBox(this HtmlHelper htmlHelper, string name, bool isChecked)
+ {
+ return CheckBox(htmlHelper, name, isChecked, htmlAttributes: (object)null);
+ }
+
+ public static MvcHtmlString CheckBox(this HtmlHelper htmlHelper, string name, bool isChecked, object htmlAttributes)
+ {
+ return CheckBox(htmlHelper, name, isChecked, HtmlHelper.AnonymousObjectToHtmlAttributes(htmlAttributes));
+ }
+
+ public static MvcHtmlString CheckBox(this HtmlHelper htmlHelper, string name, object htmlAttributes)
+ {
+ return CheckBox(htmlHelper, name, HtmlHelper.AnonymousObjectToHtmlAttributes(htmlAttributes));
+ }
+
+ public static MvcHtmlString CheckBox(this HtmlHelper htmlHelper, string name, IDictionary<string, object> htmlAttributes)
+ {
+ return CheckBoxHelper(htmlHelper, metadata: null, name: name, isChecked: null, htmlAttributes: htmlAttributes);
+ }
+
+ public static MvcHtmlString CheckBox(this HtmlHelper htmlHelper, string name, bool isChecked, IDictionary<string, object> htmlAttributes)
+ {
+ return CheckBoxHelper(htmlHelper, metadata: null, name: name, isChecked: isChecked, htmlAttributes: htmlAttributes);
+ }
+
+ [SuppressMessage("Microsoft.Design", "CA1006:DoNotNestGenericTypesInMemberSignatures", Justification = "This is an appropriate nesting of generic types")]
+ public static MvcHtmlString CheckBoxFor<TModel>(this HtmlHelper<TModel> htmlHelper, Expression<Func<TModel, bool>> expression)
+ {
+ return CheckBoxFor(htmlHelper, expression, htmlAttributes: null);
+ }
+
+ [SuppressMessage("Microsoft.Design", "CA1006:DoNotNestGenericTypesInMemberSignatures", Justification = "This is an appropriate nesting of generic types")]
+ public static MvcHtmlString CheckBoxFor<TModel>(this HtmlHelper<TModel> htmlHelper, Expression<Func<TModel, bool>> expression, object htmlAttributes)
+ {
+ return CheckBoxFor(htmlHelper, expression, HtmlHelper.AnonymousObjectToHtmlAttributes(htmlAttributes));
+ }
+
+ [SuppressMessage("Microsoft.Design", "CA1006:DoNotNestGenericTypesInMemberSignatures", Justification = "This is an appropriate nesting of generic types")]
+ public static MvcHtmlString CheckBoxFor<TModel>(this HtmlHelper<TModel> htmlHelper, Expression<Func<TModel, bool>> expression, IDictionary<string, object> htmlAttributes)
+ {
+ if (expression == null)
+ {
+ throw new ArgumentNullException("expression");
+ }
+
+ ModelMetadata metadata = ModelMetadata.FromLambdaExpression(expression, htmlHelper.ViewData);
+ bool? isChecked = null;
+ if (metadata.Model != null)
+ {
+ bool modelChecked;
+ if (Boolean.TryParse(metadata.Model.ToString(), out modelChecked))
+ {
+ isChecked = modelChecked;
+ }
+ }
+
+ return CheckBoxHelper(htmlHelper, metadata, ExpressionHelper.GetExpressionText(expression), isChecked, htmlAttributes);
+ }
+
+ private static MvcHtmlString CheckBoxHelper(HtmlHelper htmlHelper, ModelMetadata metadata, string name, bool? isChecked, IDictionary<string, object> htmlAttributes)
+ {
+ RouteValueDictionary attributes = ToRouteValueDictionary(htmlAttributes);
+
+ bool explicitValue = isChecked.HasValue;
+ if (explicitValue)
+ {
+ attributes.Remove("checked"); // Explicit value must override dictionary
+ }
+
+ return InputHelper(htmlHelper,
+ InputType.CheckBox,
+ metadata,
+ name,
+ value: "true",
+ useViewData: !explicitValue,
+ isChecked: isChecked ?? false,
+ setId: true,
+ isExplicitValue: false,
+ format: null,
+ htmlAttributes: attributes);
+ }
+
+ // Hidden
+
+ public static MvcHtmlString Hidden(this HtmlHelper htmlHelper, string name)
+ {
+ return Hidden(htmlHelper, name, value: null, htmlAttributes: null);
+ }
+
+ public static MvcHtmlString Hidden(this HtmlHelper htmlHelper, string name, object value)
+ {
+ return Hidden(htmlHelper, name, value, htmlAttributes: null);
+ }
+
+ public static MvcHtmlString Hidden(this HtmlHelper htmlHelper, string name, object value, object htmlAttributes)
+ {
+ return Hidden(htmlHelper, name, value, HtmlHelper.AnonymousObjectToHtmlAttributes(htmlAttributes));
+ }
+
+ public static MvcHtmlString Hidden(this HtmlHelper htmlHelper, string name, object value, IDictionary<string, object> htmlAttributes)
+ {
+ return HiddenHelper(htmlHelper,
+ metadata: null,
+ value: value,
+ useViewData: value == null,
+ expression: name,
+ htmlAttributes: htmlAttributes);
+ }
+
+ [SuppressMessage("Microsoft.Design", "CA1006:DoNotNestGenericTypesInMemberSignatures", Justification = "This is an appropriate nesting of generic types")]
+ public static MvcHtmlString HiddenFor<TModel, TProperty>(this HtmlHelper<TModel> htmlHelper, Expression<Func<TModel, TProperty>> expression)
+ {
+ return HiddenFor(htmlHelper, expression, (IDictionary<string, object>)null);
+ }
+
+ [SuppressMessage("Microsoft.Design", "CA1006:DoNotNestGenericTypesInMemberSignatures", Justification = "This is an appropriate nesting of generic types")]
+ public static MvcHtmlString HiddenFor<TModel, TProperty>(this HtmlHelper<TModel> htmlHelper, Expression<Func<TModel, TProperty>> expression, object htmlAttributes)
+ {
+ return HiddenFor(htmlHelper, expression, HtmlHelper.AnonymousObjectToHtmlAttributes(htmlAttributes));
+ }
+
+ [SuppressMessage("Microsoft.Design", "CA1006:DoNotNestGenericTypesInMemberSignatures", Justification = "This is an appropriate nesting of generic types")]
+ public static MvcHtmlString HiddenFor<TModel, TProperty>(this HtmlHelper<TModel> htmlHelper, Expression<Func<TModel, TProperty>> expression, IDictionary<string, object> htmlAttributes)
+ {
+ ModelMetadata metadata = ModelMetadata.FromLambdaExpression(expression, htmlHelper.ViewData);
+ return HiddenHelper(htmlHelper,
+ metadata,
+ metadata.Model,
+ false,
+ ExpressionHelper.GetExpressionText(expression),
+ htmlAttributes);
+ }
+
+ private static MvcHtmlString HiddenHelper(HtmlHelper htmlHelper, ModelMetadata metadata, object value, bool useViewData, string expression, IDictionary<string, object> htmlAttributes)
+ {
+ Binary binaryValue = value as Binary;
+ if (binaryValue != null)
+ {
+ value = binaryValue.ToArray();
+ }
+
+ byte[] byteArrayValue = value as byte[];
+ if (byteArrayValue != null)
+ {
+ value = Convert.ToBase64String(byteArrayValue);
+ }
+
+ return InputHelper(htmlHelper,
+ InputType.Hidden,
+ metadata,
+ expression,
+ value,
+ useViewData,
+ isChecked: false,
+ setId: true,
+ isExplicitValue: true,
+ format: null,
+ htmlAttributes: htmlAttributes);
+ }
+
+ // Password
+
+ public static MvcHtmlString Password(this HtmlHelper htmlHelper, string name)
+ {
+ return Password(htmlHelper, name, value: null);
+ }
+
+ public static MvcHtmlString Password(this HtmlHelper htmlHelper, string name, object value)
+ {
+ return Password(htmlHelper, name, value, htmlAttributes: null);
+ }
+
+ public static MvcHtmlString Password(this HtmlHelper htmlHelper, string name, object value, object htmlAttributes)
+ {
+ return Password(htmlHelper, name, value, HtmlHelper.AnonymousObjectToHtmlAttributes(htmlAttributes));
+ }
+
+ public static MvcHtmlString Password(this HtmlHelper htmlHelper, string name, object value, IDictionary<string, object> htmlAttributes)
+ {
+ return PasswordHelper(htmlHelper, metadata: null, name: name, value: value, htmlAttributes: htmlAttributes);
+ }
+
+ [SuppressMessage("Microsoft.Design", "CA1006:DoNotNestGenericTypesInMemberSignatures", Justification = "This is an appropriate nesting of generic types")]
+ public static MvcHtmlString PasswordFor<TModel, TProperty>(this HtmlHelper<TModel> htmlHelper, Expression<Func<TModel, TProperty>> expression)
+ {
+ return PasswordFor(htmlHelper, expression, htmlAttributes: null);
+ }
+
+ [SuppressMessage("Microsoft.Design", "CA1006:DoNotNestGenericTypesInMemberSignatures", Justification = "This is an appropriate nesting of generic types")]
+ public static MvcHtmlString PasswordFor<TModel, TProperty>(this HtmlHelper<TModel> htmlHelper, Expression<Func<TModel, TProperty>> expression, object htmlAttributes)
+ {
+ return PasswordFor(htmlHelper, expression, HtmlHelper.AnonymousObjectToHtmlAttributes(htmlAttributes));
+ }
+
+ [SuppressMessage("Microsoft.Design", "CA1011:ConsiderPassingBaseTypesAsParameters", Justification = "Users cannot use anonymous methods with the LambdaExpression type")]
+ [SuppressMessage("Microsoft.Design", "CA1006:DoNotNestGenericTypesInMemberSignatures", Justification = "This is an appropriate nesting of generic types")]
+ public static MvcHtmlString PasswordFor<TModel, TProperty>(this HtmlHelper<TModel> htmlHelper, Expression<Func<TModel, TProperty>> expression, IDictionary<string, object> htmlAttributes)
+ {
+ if (expression == null)
+ {
+ throw new ArgumentNullException("expression");
+ }
+
+ return PasswordHelper(htmlHelper,
+ ModelMetadata.FromLambdaExpression(expression, htmlHelper.ViewData),
+ ExpressionHelper.GetExpressionText(expression),
+ value: null,
+ htmlAttributes: htmlAttributes);
+ }
+
+ private static MvcHtmlString PasswordHelper(HtmlHelper htmlHelper, ModelMetadata metadata, string name, object value, IDictionary<string, object> htmlAttributes)
+ {
+ return InputHelper(htmlHelper,
+ InputType.Password,
+ metadata,
+ name,
+ value,
+ useViewData: false,
+ isChecked: false,
+ setId: true,
+ isExplicitValue: true,
+ format: null,
+ htmlAttributes: htmlAttributes);
+ }
+
+ // RadioButton
+
+ public static MvcHtmlString RadioButton(this HtmlHelper htmlHelper, string name, object value)
+ {
+ return RadioButton(htmlHelper, name, value, htmlAttributes: (object)null);
+ }
+
+ public static MvcHtmlString RadioButton(this HtmlHelper htmlHelper, string name, object value, object htmlAttributes)
+ {
+ return RadioButton(htmlHelper, name, value, HtmlHelper.AnonymousObjectToHtmlAttributes(htmlAttributes));
+ }
+
+ public static MvcHtmlString RadioButton(this HtmlHelper htmlHelper, string name, object value, IDictionary<string, object> htmlAttributes)
+ {
+ // Determine whether or not to render the checked attribute based on the contents of ViewData.
+ string valueString = Convert.ToString(value, CultureInfo.CurrentCulture);
+ bool isChecked = (!String.IsNullOrEmpty(name)) && (String.Equals(htmlHelper.EvalString(name), valueString, StringComparison.OrdinalIgnoreCase));
+ // checked attributes is implicit, so we need to ensure that the dictionary takes precedence.
+ RouteValueDictionary attributes = ToRouteValueDictionary(htmlAttributes);
+ if (attributes.ContainsKey("checked"))
+ {
+ return InputHelper(htmlHelper,
+ InputType.Radio,
+ metadata: null,
+ name: name,
+ value: value,
+ useViewData: false,
+ isChecked: false,
+ setId: true,
+ isExplicitValue: true,
+ format: null,
+ htmlAttributes: attributes);
+ }
+
+ return RadioButton(htmlHelper, name, value, isChecked, htmlAttributes);
+ }
+
+ public static MvcHtmlString RadioButton(this HtmlHelper htmlHelper, string name, object value, bool isChecked)
+ {
+ return RadioButton(htmlHelper, name, value, isChecked, htmlAttributes: (object)null);
+ }
+
+ public static MvcHtmlString RadioButton(this HtmlHelper htmlHelper, string name, object value, bool isChecked, object htmlAttributes)
+ {
+ return RadioButton(htmlHelper, name, value, isChecked, HtmlHelper.AnonymousObjectToHtmlAttributes(htmlAttributes));
+ }
+
+ public static MvcHtmlString RadioButton(this HtmlHelper htmlHelper, string name, object value, bool isChecked, IDictionary<string, object> htmlAttributes)
+ {
+ if (value == null)
+ {
+ throw new ArgumentNullException("value");
+ }
+ // checked attribute is an explicit parameter so it takes precedence.
+ RouteValueDictionary attributes = ToRouteValueDictionary(htmlAttributes);
+ attributes.Remove("checked");
+ return InputHelper(htmlHelper,
+ InputType.Radio,
+ metadata: null,
+ name: name,
+ value: value,
+ useViewData: false,
+ isChecked: isChecked,
+ setId: true,
+ isExplicitValue: true,
+ format: null,
+ htmlAttributes: attributes);
+ }
+
+ [SuppressMessage("Microsoft.Design", "CA1006:DoNotNestGenericTypesInMemberSignatures", Justification = "This is an appropriate nesting of generic types")]
+ public static MvcHtmlString RadioButtonFor<TModel, TProperty>(this HtmlHelper<TModel> htmlHelper, Expression<Func<TModel, TProperty>> expression, object value)
+ {
+ return RadioButtonFor(htmlHelper, expression, value, htmlAttributes: null);
+ }
+
+ [SuppressMessage("Microsoft.Design", "CA1006:DoNotNestGenericTypesInMemberSignatures", Justification = "This is an appropriate nesting of generic types")]
+ public static MvcHtmlString RadioButtonFor<TModel, TProperty>(this HtmlHelper<TModel> htmlHelper, Expression<Func<TModel, TProperty>> expression, object value, object htmlAttributes)
+ {
+ return RadioButtonFor(htmlHelper, expression, value, HtmlHelper.AnonymousObjectToHtmlAttributes(htmlAttributes));
+ }
+
+ [SuppressMessage("Microsoft.Design", "CA1006:DoNotNestGenericTypesInMemberSignatures", Justification = "This is an appropriate nesting of generic types")]
+ public static MvcHtmlString RadioButtonFor<TModel, TProperty>(this HtmlHelper<TModel> htmlHelper, Expression<Func<TModel, TProperty>> expression, object value, IDictionary<string, object> htmlAttributes)
+ {
+ ModelMetadata metadata = ModelMetadata.FromLambdaExpression(expression, htmlHelper.ViewData);
+ return RadioButtonHelper(htmlHelper,
+ metadata,
+ metadata.Model,
+ ExpressionHelper.GetExpressionText(expression),
+ value,
+ null /* isChecked */,
+ htmlAttributes);
+ }
+
+ private static MvcHtmlString RadioButtonHelper(HtmlHelper htmlHelper, ModelMetadata metadata, object model, string name, object value, bool? isChecked, IDictionary<string, object> htmlAttributes)
+ {
+ if (value == null)
+ {
+ throw new ArgumentNullException("value");
+ }
+
+ RouteValueDictionary attributes = ToRouteValueDictionary(htmlAttributes);
+
+ bool explicitValue = isChecked.HasValue;
+ if (explicitValue)
+ {
+ attributes.Remove("checked"); // Explicit value must override dictionary
+ }
+ else
+ {
+ string valueString = Convert.ToString(value, CultureInfo.CurrentCulture);
+ isChecked = model != null &&
+ !String.IsNullOrEmpty(name) &&
+ String.Equals(model.ToString(), valueString, StringComparison.OrdinalIgnoreCase);
+ }
+
+ return InputHelper(htmlHelper,
+ InputType.Radio,
+ metadata,
+ name,
+ value,
+ useViewData: false,
+ isChecked: isChecked ?? false,
+ setId: true,
+ isExplicitValue: true,
+ format: null,
+ htmlAttributes: attributes);
+ }
+
+ // TextBox
+
+ public static MvcHtmlString TextBox(this HtmlHelper htmlHelper, string name)
+ {
+ return TextBox(htmlHelper, name, value: null);
+ }
+
+ public static MvcHtmlString TextBox(this HtmlHelper htmlHelper, string name, object value)
+ {
+ return TextBox(htmlHelper, name, value, format: null);
+ }
+
+ public static MvcHtmlString TextBox(this HtmlHelper htmlHelper, string name, object value, string format)
+ {
+ return TextBox(htmlHelper, name, value, format, htmlAttributes: (object)null);
+ }
+
+ public static MvcHtmlString TextBox(this HtmlHelper htmlHelper, string name, object value, object htmlAttributes)
+ {
+ return TextBox(htmlHelper, name, value, format: null, htmlAttributes: htmlAttributes);
+ }
+
+ public static MvcHtmlString TextBox(this HtmlHelper htmlHelper, string name, object value, string format, object htmlAttributes)
+ {
+ return TextBox(htmlHelper, name, value, format, HtmlHelper.AnonymousObjectToHtmlAttributes(htmlAttributes));
+ }
+
+ public static MvcHtmlString TextBox(this HtmlHelper htmlHelper, string name, object value, IDictionary<string, object> htmlAttributes)
+ {
+ return TextBox(htmlHelper, name, value, format: null, htmlAttributes: htmlAttributes);
+ }
+
+ public static MvcHtmlString TextBox(this HtmlHelper htmlHelper, string name, object value, string format, IDictionary<string, object> htmlAttributes)
+ {
+ return InputHelper(htmlHelper,
+ InputType.Text,
+ metadata: null,
+ name: name,
+ value: value,
+ useViewData: (value == null),
+ isChecked: false,
+ setId: true,
+ isExplicitValue: true,
+ format: format,
+ htmlAttributes: htmlAttributes);
+ }
+
+ [SuppressMessage("Microsoft.Design", "CA1006:DoNotNestGenericTypesInMemberSignatures", Justification = "This is an appropriate nesting of generic types")]
+ public static MvcHtmlString TextBoxFor<TModel, TProperty>(this HtmlHelper<TModel> htmlHelper, Expression<Func<TModel, TProperty>> expression)
+ {
+ return htmlHelper.TextBoxFor(expression, format: null);
+ }
+
+ [SuppressMessage("Microsoft.Design", "CA1006:DoNotNestGenericTypesInMemberSignatures", Justification = "This is an appropriate nesting of generic types")]
+ public static MvcHtmlString TextBoxFor<TModel, TProperty>(this HtmlHelper<TModel> htmlHelper, Expression<Func<TModel, TProperty>> expression, string format)
+ {
+ return htmlHelper.TextBoxFor(expression, format, (IDictionary<string, object>)null);
+ }
+
+ [SuppressMessage("Microsoft.Design", "CA1006:DoNotNestGenericTypesInMemberSignatures", Justification = "This is an appropriate nesting of generic types")]
+ public static MvcHtmlString TextBoxFor<TModel, TProperty>(this HtmlHelper<TModel> htmlHelper, Expression<Func<TModel, TProperty>> expression, object htmlAttributes)
+ {
+ return htmlHelper.TextBoxFor(expression, format: null, htmlAttributes: htmlAttributes);
+ }
+
+ [SuppressMessage("Microsoft.Design", "CA1006:DoNotNestGenericTypesInMemberSignatures", Justification = "This is an appropriate nesting of generic types")]
+ public static MvcHtmlString TextBoxFor<TModel, TProperty>(this HtmlHelper<TModel> htmlHelper, Expression<Func<TModel, TProperty>> expression, string format, object htmlAttributes)
+ {
+ return htmlHelper.TextBoxFor(expression, format: format, htmlAttributes: HtmlHelper.AnonymousObjectToHtmlAttributes(htmlAttributes));
+ }
+
+ [SuppressMessage("Microsoft.Design", "CA1006:DoNotNestGenericTypesInMemberSignatures", Justification = "This is an appropriate nesting of generic types")]
+ public static MvcHtmlString TextBoxFor<TModel, TProperty>(this HtmlHelper<TModel> htmlHelper, Expression<Func<TModel, TProperty>> expression, IDictionary<string, object> htmlAttributes)
+ {
+ return htmlHelper.TextBoxFor(expression, format: null, htmlAttributes: htmlAttributes);
+ }
+
+ [SuppressMessage("Microsoft.Design", "CA1006:DoNotNestGenericTypesInMemberSignatures", Justification = "This is an appropriate nesting of generic types")]
+ public static MvcHtmlString TextBoxFor<TModel, TProperty>(this HtmlHelper<TModel> htmlHelper, Expression<Func<TModel, TProperty>> expression, string format, IDictionary<string, object> htmlAttributes)
+ {
+ ModelMetadata metadata = ModelMetadata.FromLambdaExpression(expression, htmlHelper.ViewData);
+ return TextBoxHelper(htmlHelper,
+ metadata,
+ metadata.Model,
+ ExpressionHelper.GetExpressionText(expression),
+ format,
+ htmlAttributes);
+ }
+
+ private static MvcHtmlString TextBoxHelper(this HtmlHelper htmlHelper, ModelMetadata metadata, object model, string expression, string format, IDictionary<string, object> htmlAttributes)
+ {
+ return InputHelper(htmlHelper,
+ InputType.Text,
+ metadata,
+ expression,
+ model,
+ useViewData: false,
+ isChecked: false,
+ setId: true,
+ isExplicitValue: true,
+ format: format,
+ htmlAttributes: htmlAttributes);
+ }
+
+ // Helper methods
+
+ private static MvcHtmlString InputHelper(HtmlHelper htmlHelper, InputType inputType, ModelMetadata metadata, string name, object value, bool useViewData, bool isChecked, bool setId, bool isExplicitValue, string format, IDictionary<string, object> htmlAttributes)
+ {
+ string fullName = htmlHelper.ViewContext.ViewData.TemplateInfo.GetFullHtmlFieldName(name);
+ if (String.IsNullOrEmpty(fullName))
+ {
+ throw new ArgumentException(MvcResources.Common_NullOrEmpty, "name");
+ }
+
+ TagBuilder tagBuilder = new TagBuilder("input");
+ tagBuilder.MergeAttributes(htmlAttributes);
+ tagBuilder.MergeAttribute("type", HtmlHelper.GetInputTypeString(inputType));
+ tagBuilder.MergeAttribute("name", fullName, true);
+
+ string valueParameter = htmlHelper.FormatValue(value, format);
+ bool usedModelState = false;
+
+ switch (inputType)
+ {
+ case InputType.CheckBox:
+ bool? modelStateWasChecked = htmlHelper.GetModelStateValue(fullName, typeof(bool)) as bool?;
+ if (modelStateWasChecked.HasValue)
+ {
+ isChecked = modelStateWasChecked.Value;
+ usedModelState = true;
+ }
+ goto case InputType.Radio;
+ case InputType.Radio:
+ if (!usedModelState)
+ {
+ string modelStateValue = htmlHelper.GetModelStateValue(fullName, typeof(string)) as string;
+ if (modelStateValue != null)
+ {
+ isChecked = String.Equals(modelStateValue, valueParameter, StringComparison.Ordinal);
+ usedModelState = true;
+ }
+ }
+ if (!usedModelState && useViewData)
+ {
+ isChecked = htmlHelper.EvalBoolean(fullName);
+ }
+ if (isChecked)
+ {
+ tagBuilder.MergeAttribute("checked", "checked");
+ }
+ tagBuilder.MergeAttribute("value", valueParameter, isExplicitValue);
+ break;
+ case InputType.Password:
+ if (value != null)
+ {
+ tagBuilder.MergeAttribute("value", valueParameter, isExplicitValue);
+ }
+ break;
+ default:
+ string attemptedValue = (string)htmlHelper.GetModelStateValue(fullName, typeof(string));
+ tagBuilder.MergeAttribute("value", attemptedValue ?? ((useViewData) ? htmlHelper.EvalString(fullName, format) : valueParameter), isExplicitValue);
+ break;
+ }
+
+ if (setId)
+ {
+ tagBuilder.GenerateId(fullName);
+ }
+
+ // If there are any errors for a named field, we add the css attribute.
+ ModelState modelState;
+ if (htmlHelper.ViewData.ModelState.TryGetValue(fullName, out modelState))
+ {
+ if (modelState.Errors.Count > 0)
+ {
+ tagBuilder.AddCssClass(HtmlHelper.ValidationInputCssClassName);
+ }
+ }
+
+ tagBuilder.MergeAttributes(htmlHelper.GetUnobtrusiveValidationAttributes(name, metadata));
+
+ if (inputType == InputType.CheckBox)
+ {
+ // Render an additional <input type="hidden".../> for checkboxes. This
+ // addresses scenarios where unchecked checkboxes are not sent in the request.
+ // Sending a hidden input makes it possible to know that the checkbox was present
+ // on the page when the request was submitted.
+ StringBuilder inputItemBuilder = new StringBuilder();
+ inputItemBuilder.Append(tagBuilder.ToString(TagRenderMode.SelfClosing));
+
+ TagBuilder hiddenInput = new TagBuilder("input");
+ hiddenInput.MergeAttribute("type", HtmlHelper.GetInputTypeString(InputType.Hidden));
+ hiddenInput.MergeAttribute("name", fullName);
+ hiddenInput.MergeAttribute("value", "false");
+ inputItemBuilder.Append(hiddenInput.ToString(TagRenderMode.SelfClosing));
+ return MvcHtmlString.Create(inputItemBuilder.ToString());
+ }
+
+ return tagBuilder.ToMvcHtmlString(TagRenderMode.SelfClosing);
+ }
+
+ private static RouteValueDictionary ToRouteValueDictionary(IDictionary<string, object> dictionary)
+ {
+ return dictionary == null ? new RouteValueDictionary() : new RouteValueDictionary(dictionary);
+ }
+ }
+}
diff --git a/src/System.Web.Mvc/Html/LabelExtensions.cs b/src/System.Web.Mvc/Html/LabelExtensions.cs
new file mode 100644
index 00000000..96c06b2e
--- /dev/null
+++ b/src/System.Web.Mvc/Html/LabelExtensions.cs
@@ -0,0 +1,159 @@
+using System.Collections.Generic;
+using System.Diagnostics.CodeAnalysis;
+using System.Linq;
+using System.Linq.Expressions;
+
+namespace System.Web.Mvc.Html
+{
+ public static class LabelExtensions
+ {
+ public static MvcHtmlString Label(this HtmlHelper html, string expression)
+ {
+ return Label(html,
+ expression,
+ labelText: null);
+ }
+
+ public static MvcHtmlString Label(this HtmlHelper html, string expression, string labelText)
+ {
+ return Label(html, expression, labelText, htmlAttributes: null, metadataProvider: null);
+ }
+
+ public static MvcHtmlString Label(this HtmlHelper html, string expression, object htmlAttributes)
+ {
+ return Label(html, expression, labelText: null, htmlAttributes: htmlAttributes, metadataProvider: null);
+ }
+
+ public static MvcHtmlString Label(this HtmlHelper html, string expression, IDictionary<string, object> htmlAttributes)
+ {
+ return Label(html, expression, labelText: null, htmlAttributes: htmlAttributes, metadataProvider: null);
+ }
+
+ public static MvcHtmlString Label(this HtmlHelper html, string expression, string labelText, object htmlAttributes)
+ {
+ return Label(html, expression, labelText, htmlAttributes, metadataProvider: null);
+ }
+
+ public static MvcHtmlString Label(this HtmlHelper html, string expression, string labelText, IDictionary<string, object> htmlAttributes)
+ {
+ return Label(html, expression, labelText, htmlAttributes, metadataProvider: null);
+ }
+
+ internal static MvcHtmlString Label(this HtmlHelper html, string expression, string labelText, object htmlAttributes, ModelMetadataProvider metadataProvider)
+ {
+ return Label(html,
+ expression,
+ labelText,
+ HtmlHelper.AnonymousObjectToHtmlAttributes(htmlAttributes),
+ metadataProvider);
+ }
+
+ internal static MvcHtmlString Label(this HtmlHelper html, string expression, string labelText, IDictionary<string, object> htmlAttributes, ModelMetadataProvider metadataProvider)
+ {
+ return LabelHelper(html,
+ ModelMetadata.FromStringExpression(expression, html.ViewData, metadataProvider),
+ expression,
+ labelText,
+ htmlAttributes);
+ }
+
+ [SuppressMessage("Microsoft.Design", "CA1006:DoNotNestGenericTypesInMemberSignatures", Justification = "This is an appropriate nesting of generic types")]
+ public static MvcHtmlString LabelFor<TModel, TValue>(this HtmlHelper<TModel> html, Expression<Func<TModel, TValue>> expression)
+ {
+ return LabelFor<TModel, TValue>(html, expression, labelText: null);
+ }
+
+ [SuppressMessage("Microsoft.Design", "CA1006:DoNotNestGenericTypesInMemberSignatures", Justification = "This is an appropriate nesting of generic types")]
+ public static MvcHtmlString LabelFor<TModel, TValue>(this HtmlHelper<TModel> html, Expression<Func<TModel, TValue>> expression, string labelText)
+ {
+ return LabelFor(html, expression, labelText, htmlAttributes: null, metadataProvider: null);
+ }
+
+ [SuppressMessage("Microsoft.Design", "CA1006:DoNotNestGenericTypesInMemberSignatures", Justification = "This is an appropriate nesting of generic types")]
+ public static MvcHtmlString LabelFor<TModel, TValue>(this HtmlHelper<TModel> html, Expression<Func<TModel, TValue>> expression, object htmlAttributes)
+ {
+ return LabelFor(html, expression, labelText: null, htmlAttributes: htmlAttributes, metadataProvider: null);
+ }
+
+ [SuppressMessage("Microsoft.Design", "CA1006:DoNotNestGenericTypesInMemberSignatures", Justification = "This is an appropriate nesting of generic types")]
+ public static MvcHtmlString LabelFor<TModel, TValue>(this HtmlHelper<TModel> html, Expression<Func<TModel, TValue>> expression, IDictionary<string, object> htmlAttributes)
+ {
+ return LabelFor(html, expression, labelText: null, htmlAttributes: htmlAttributes, metadataProvider: null);
+ }
+
+ [SuppressMessage("Microsoft.Design", "CA1006:DoNotNestGenericTypesInMemberSignatures", Justification = "This is an appropriate nesting of generic types")]
+ public static MvcHtmlString LabelFor<TModel, TValue>(this HtmlHelper<TModel> html, Expression<Func<TModel, TValue>> expression, string labelText, object htmlAttributes)
+ {
+ return LabelFor(html, expression, labelText, htmlAttributes, metadataProvider: null);
+ }
+
+ [SuppressMessage("Microsoft.Design", "CA1006:DoNotNestGenericTypesInMemberSignatures", Justification = "This is an appropriate nesting of generic types")]
+ public static MvcHtmlString LabelFor<TModel, TValue>(this HtmlHelper<TModel> html, Expression<Func<TModel, TValue>> expression, string labelText, IDictionary<string, object> htmlAttributes)
+ {
+ return LabelFor(html, expression, labelText, htmlAttributes, metadataProvider: null);
+ }
+
+ internal static MvcHtmlString LabelFor<TModel, TValue>(this HtmlHelper<TModel> html, Expression<Func<TModel, TValue>> expression, string labelText, object htmlAttributes, ModelMetadataProvider metadataProvider)
+ {
+ return LabelFor(html,
+ expression,
+ labelText,
+ HtmlHelper.AnonymousObjectToHtmlAttributes(htmlAttributes),
+ metadataProvider);
+ }
+
+ internal static MvcHtmlString LabelFor<TModel, TValue>(this HtmlHelper<TModel> html, Expression<Func<TModel, TValue>> expression, string labelText, IDictionary<string, object> htmlAttributes, ModelMetadataProvider metadataProvider)
+ {
+ return LabelHelper(html,
+ ModelMetadata.FromLambdaExpression(expression, html.ViewData, metadataProvider),
+ ExpressionHelper.GetExpressionText(expression),
+ labelText,
+ htmlAttributes);
+ }
+
+ public static MvcHtmlString LabelForModel(this HtmlHelper html)
+ {
+ return LabelForModel(html, labelText: null);
+ }
+
+ public static MvcHtmlString LabelForModel(this HtmlHelper html, string labelText)
+ {
+ return LabelHelper(html, html.ViewData.ModelMetadata, String.Empty, labelText);
+ }
+
+ public static MvcHtmlString LabelForModel(this HtmlHelper html, object htmlAttributes)
+ {
+ return LabelHelper(html, html.ViewData.ModelMetadata, String.Empty, labelText: null, htmlAttributes: HtmlHelper.AnonymousObjectToHtmlAttributes(htmlAttributes));
+ }
+
+ public static MvcHtmlString LabelForModel(this HtmlHelper html, IDictionary<string, object> htmlAttributes)
+ {
+ return LabelHelper(html, html.ViewData.ModelMetadata, String.Empty, labelText: null, htmlAttributes: htmlAttributes);
+ }
+
+ public static MvcHtmlString LabelForModel(this HtmlHelper html, string labelText, object htmlAttributes)
+ {
+ return LabelHelper(html, html.ViewData.ModelMetadata, String.Empty, labelText, HtmlHelper.AnonymousObjectToHtmlAttributes(htmlAttributes));
+ }
+
+ public static MvcHtmlString LabelForModel(this HtmlHelper html, string labelText, IDictionary<string, object> htmlAttributes)
+ {
+ return LabelHelper(html, html.ViewData.ModelMetadata, String.Empty, labelText, htmlAttributes);
+ }
+
+ internal static MvcHtmlString LabelHelper(HtmlHelper html, ModelMetadata metadata, string htmlFieldName, string labelText = null, IDictionary<string, object> htmlAttributes = null)
+ {
+ string resolvedLabelText = labelText ?? metadata.DisplayName ?? metadata.PropertyName ?? htmlFieldName.Split('.').Last();
+ if (String.IsNullOrEmpty(resolvedLabelText))
+ {
+ return MvcHtmlString.Empty;
+ }
+
+ TagBuilder tag = new TagBuilder("label");
+ tag.Attributes.Add("for", TagBuilder.CreateSanitizedId(html.ViewContext.ViewData.TemplateInfo.GetFullHtmlFieldName(htmlFieldName)));
+ tag.SetInnerText(resolvedLabelText);
+ tag.MergeAttributes(htmlAttributes, replaceExisting: true);
+ return tag.ToMvcHtmlString(TagRenderMode.Normal);
+ }
+ }
+}
diff --git a/src/System.Web.Mvc/Html/LinkExtensions.cs b/src/System.Web.Mvc/Html/LinkExtensions.cs
new file mode 100644
index 00000000..cde4318c
--- /dev/null
+++ b/src/System.Web.Mvc/Html/LinkExtensions.cs
@@ -0,0 +1,130 @@
+using System.Collections.Generic;
+using System.Web.Mvc.Properties;
+using System.Web.Routing;
+
+namespace System.Web.Mvc.Html
+{
+ public static class LinkExtensions
+ {
+ public static MvcHtmlString ActionLink(this HtmlHelper htmlHelper, string linkText, string actionName)
+ {
+ return ActionLink(htmlHelper, linkText, actionName, null /* controllerName */, new RouteValueDictionary(), new RouteValueDictionary());
+ }
+
+ public static MvcHtmlString ActionLink(this HtmlHelper htmlHelper, string linkText, string actionName, object routeValues)
+ {
+ return ActionLink(htmlHelper, linkText, actionName, null /* controllerName */, new RouteValueDictionary(routeValues), new RouteValueDictionary());
+ }
+
+ public static MvcHtmlString ActionLink(this HtmlHelper htmlHelper, string linkText, string actionName, object routeValues, object htmlAttributes)
+ {
+ return ActionLink(htmlHelper, linkText, actionName, null /* controllerName */, new RouteValueDictionary(routeValues), HtmlHelper.AnonymousObjectToHtmlAttributes(htmlAttributes));
+ }
+
+ public static MvcHtmlString ActionLink(this HtmlHelper htmlHelper, string linkText, string actionName, RouteValueDictionary routeValues)
+ {
+ return ActionLink(htmlHelper, linkText, actionName, null /* controllerName */, routeValues, new RouteValueDictionary());
+ }
+
+ public static MvcHtmlString ActionLink(this HtmlHelper htmlHelper, string linkText, string actionName, RouteValueDictionary routeValues, IDictionary<string, object> htmlAttributes)
+ {
+ return ActionLink(htmlHelper, linkText, actionName, null /* controllerName */, routeValues, htmlAttributes);
+ }
+
+ public static MvcHtmlString ActionLink(this HtmlHelper htmlHelper, string linkText, string actionName, string controllerName)
+ {
+ return ActionLink(htmlHelper, linkText, actionName, controllerName, new RouteValueDictionary(), new RouteValueDictionary());
+ }
+
+ public static MvcHtmlString ActionLink(this HtmlHelper htmlHelper, string linkText, string actionName, string controllerName, object routeValues, object htmlAttributes)
+ {
+ return ActionLink(htmlHelper, linkText, actionName, controllerName, new RouteValueDictionary(routeValues), HtmlHelper.AnonymousObjectToHtmlAttributes(htmlAttributes));
+ }
+
+ public static MvcHtmlString ActionLink(this HtmlHelper htmlHelper, string linkText, string actionName, string controllerName, RouteValueDictionary routeValues, IDictionary<string, object> htmlAttributes)
+ {
+ if (String.IsNullOrEmpty(linkText))
+ {
+ throw new ArgumentException(MvcResources.Common_NullOrEmpty, "linkText");
+ }
+ return MvcHtmlString.Create(HtmlHelper.GenerateLink(htmlHelper.ViewContext.RequestContext, htmlHelper.RouteCollection, linkText, null /* routeName */, actionName, controllerName, routeValues, htmlAttributes));
+ }
+
+ public static MvcHtmlString ActionLink(this HtmlHelper htmlHelper, string linkText, string actionName, string controllerName, string protocol, string hostName, string fragment, object routeValues, object htmlAttributes)
+ {
+ return ActionLink(htmlHelper, linkText, actionName, controllerName, protocol, hostName, fragment, new RouteValueDictionary(routeValues), HtmlHelper.AnonymousObjectToHtmlAttributes(htmlAttributes));
+ }
+
+ public static MvcHtmlString ActionLink(this HtmlHelper htmlHelper, string linkText, string actionName, string controllerName, string protocol, string hostName, string fragment, RouteValueDictionary routeValues, IDictionary<string, object> htmlAttributes)
+ {
+ if (String.IsNullOrEmpty(linkText))
+ {
+ throw new ArgumentException(MvcResources.Common_NullOrEmpty, "linkText");
+ }
+ return MvcHtmlString.Create(HtmlHelper.GenerateLink(htmlHelper.ViewContext.RequestContext, htmlHelper.RouteCollection, linkText, null /* routeName */, actionName, controllerName, protocol, hostName, fragment, routeValues, htmlAttributes));
+ }
+
+ public static MvcHtmlString RouteLink(this HtmlHelper htmlHelper, string linkText, object routeValues)
+ {
+ return RouteLink(htmlHelper, linkText, new RouteValueDictionary(routeValues));
+ }
+
+ public static MvcHtmlString RouteLink(this HtmlHelper htmlHelper, string linkText, RouteValueDictionary routeValues)
+ {
+ return RouteLink(htmlHelper, linkText, routeValues, new RouteValueDictionary());
+ }
+
+ public static MvcHtmlString RouteLink(this HtmlHelper htmlHelper, string linkText, string routeName)
+ {
+ return RouteLink(htmlHelper, linkText, routeName, (object)null /* routeValues */);
+ }
+
+ public static MvcHtmlString RouteLink(this HtmlHelper htmlHelper, string linkText, string routeName, object routeValues)
+ {
+ return RouteLink(htmlHelper, linkText, routeName, new RouteValueDictionary(routeValues));
+ }
+
+ public static MvcHtmlString RouteLink(this HtmlHelper htmlHelper, string linkText, string routeName, RouteValueDictionary routeValues)
+ {
+ return RouteLink(htmlHelper, linkText, routeName, routeValues, new RouteValueDictionary());
+ }
+
+ public static MvcHtmlString RouteLink(this HtmlHelper htmlHelper, string linkText, object routeValues, object htmlAttributes)
+ {
+ return RouteLink(htmlHelper, linkText, new RouteValueDictionary(routeValues), HtmlHelper.AnonymousObjectToHtmlAttributes(htmlAttributes));
+ }
+
+ public static MvcHtmlString RouteLink(this HtmlHelper htmlHelper, string linkText, RouteValueDictionary routeValues, IDictionary<string, object> htmlAttributes)
+ {
+ return RouteLink(htmlHelper, linkText, null /* routeName */, routeValues, htmlAttributes);
+ }
+
+ public static MvcHtmlString RouteLink(this HtmlHelper htmlHelper, string linkText, string routeName, object routeValues, object htmlAttributes)
+ {
+ return RouteLink(htmlHelper, linkText, routeName, new RouteValueDictionary(routeValues), HtmlHelper.AnonymousObjectToHtmlAttributes(htmlAttributes));
+ }
+
+ public static MvcHtmlString RouteLink(this HtmlHelper htmlHelper, string linkText, string routeName, RouteValueDictionary routeValues, IDictionary<string, object> htmlAttributes)
+ {
+ if (String.IsNullOrEmpty(linkText))
+ {
+ throw new ArgumentException(MvcResources.Common_NullOrEmpty, "linkText");
+ }
+ return MvcHtmlString.Create(HtmlHelper.GenerateRouteLink(htmlHelper.ViewContext.RequestContext, htmlHelper.RouteCollection, linkText, routeName, routeValues, htmlAttributes));
+ }
+
+ public static MvcHtmlString RouteLink(this HtmlHelper htmlHelper, string linkText, string routeName, string protocol, string hostName, string fragment, object routeValues, object htmlAttributes)
+ {
+ return RouteLink(htmlHelper, linkText, routeName, protocol, hostName, fragment, new RouteValueDictionary(routeValues), HtmlHelper.AnonymousObjectToHtmlAttributes(htmlAttributes));
+ }
+
+ public static MvcHtmlString RouteLink(this HtmlHelper htmlHelper, string linkText, string routeName, string protocol, string hostName, string fragment, RouteValueDictionary routeValues, IDictionary<string, object> htmlAttributes)
+ {
+ if (String.IsNullOrEmpty(linkText))
+ {
+ throw new ArgumentException(MvcResources.Common_NullOrEmpty, "linkText");
+ }
+ return MvcHtmlString.Create(HtmlHelper.GenerateRouteLink(htmlHelper.ViewContext.RequestContext, htmlHelper.RouteCollection, linkText, routeName, protocol, hostName, fragment, routeValues, htmlAttributes));
+ }
+ }
+}
diff --git a/src/System.Web.Mvc/Html/MvcForm.cs b/src/System.Web.Mvc/Html/MvcForm.cs
new file mode 100644
index 00000000..1d700468
--- /dev/null
+++ b/src/System.Web.Mvc/Html/MvcForm.cs
@@ -0,0 +1,51 @@
+using System.Diagnostics.CodeAnalysis;
+using System.Web.Mvc.Properties;
+
+namespace System.Web.Mvc.Html
+{
+ public class MvcForm : IDisposable
+ {
+ private readonly ViewContext _viewContext;
+ private bool _disposed;
+
+ [SuppressMessage("Microsoft.Usage", "CA1801:ReviewUnusedParameters", MessageId = "httpResponse", Justification = "This method existed in MVC 1.0 and has been deprecated.")]
+ [Obsolete("This constructor is obsolete, because its functionality has been moved to MvcForm(ViewContext) now.", true /* error */)]
+ public MvcForm(HttpResponseBase httpResponse)
+ {
+ throw new InvalidOperationException(MvcResources.MvcForm_ConstructorObsolete);
+ }
+
+ public MvcForm(ViewContext viewContext)
+ {
+ if (viewContext == null)
+ {
+ throw new ArgumentNullException("viewContext");
+ }
+
+ _viewContext = viewContext;
+
+ // push the new FormContext
+ _viewContext.FormContext = new FormContext();
+ }
+
+ public void Dispose()
+ {
+ Dispose(true /* disposing */);
+ GC.SuppressFinalize(this);
+ }
+
+ protected virtual void Dispose(bool disposing)
+ {
+ if (!_disposed)
+ {
+ _disposed = true;
+ FormExtensions.EndForm(_viewContext);
+ }
+ }
+
+ public void EndForm()
+ {
+ Dispose(true);
+ }
+ }
+}
diff --git a/src/System.Web.Mvc/Html/NameExtensions.cs b/src/System.Web.Mvc/Html/NameExtensions.cs
new file mode 100644
index 00000000..9d5f9b16
--- /dev/null
+++ b/src/System.Web.Mvc/Html/NameExtensions.cs
@@ -0,0 +1,43 @@
+using System.Diagnostics.CodeAnalysis;
+using System.Linq.Expressions;
+
+namespace System.Web.Mvc.Html
+{
+ public static class NameExtensions
+ {
+ public static MvcHtmlString Id(this HtmlHelper html, string name)
+ {
+ return MvcHtmlString.Create(html.AttributeEncode(html.ViewData.TemplateInfo.GetFullHtmlFieldId(name)));
+ }
+
+ [SuppressMessage("Microsoft.Design", "CA1006:DoNotNestGenericTypesInMemberSignatures", Justification = "This is an appropriate nesting of generic types")]
+ [SuppressMessage("Microsoft.Design", "CA1011:ConsiderPassingBaseTypesAsParameters", Justification = "Users cannot use anonymous methods with the LambdaExpression type")]
+ public static MvcHtmlString IdFor<TModel, TProperty>(this HtmlHelper<TModel> html, Expression<Func<TModel, TProperty>> expression)
+ {
+ return Id(html, ExpressionHelper.GetExpressionText(expression));
+ }
+
+ public static MvcHtmlString IdForModel(this HtmlHelper html)
+ {
+ return Id(html, String.Empty);
+ }
+
+ [SuppressMessage("Microsoft.Naming", "CA1719:ParameterNamesShouldNotMatchMemberNames", MessageId = "1#", Justification = "This is a shipped API.")]
+ public static MvcHtmlString Name(this HtmlHelper html, string name)
+ {
+ return MvcHtmlString.Create(html.AttributeEncode(html.ViewData.TemplateInfo.GetFullHtmlFieldName(name)));
+ }
+
+ [SuppressMessage("Microsoft.Design", "CA1006:DoNotNestGenericTypesInMemberSignatures", Justification = "This is an appropriate nesting of generic types")]
+ [SuppressMessage("Microsoft.Design", "CA1011:ConsiderPassingBaseTypesAsParameters", Justification = "Users cannot use anonymous methods with the LambdaExpression type")]
+ public static MvcHtmlString NameFor<TModel, TProperty>(this HtmlHelper<TModel> html, Expression<Func<TModel, TProperty>> expression)
+ {
+ return Name(html, ExpressionHelper.GetExpressionText(expression));
+ }
+
+ public static MvcHtmlString NameForModel(this HtmlHelper html)
+ {
+ return Name(html, String.Empty);
+ }
+ }
+}
diff --git a/src/System.Web.Mvc/Html/PartialExtensions.cs b/src/System.Web.Mvc/Html/PartialExtensions.cs
new file mode 100644
index 00000000..ee3dcc7d
--- /dev/null
+++ b/src/System.Web.Mvc/Html/PartialExtensions.cs
@@ -0,0 +1,32 @@
+using System.Globalization;
+using System.IO;
+
+namespace System.Web.Mvc.Html
+{
+ public static class PartialExtensions
+ {
+ public static MvcHtmlString Partial(this HtmlHelper htmlHelper, string partialViewName)
+ {
+ return Partial(htmlHelper, partialViewName, null /* model */, htmlHelper.ViewData);
+ }
+
+ public static MvcHtmlString Partial(this HtmlHelper htmlHelper, string partialViewName, ViewDataDictionary viewData)
+ {
+ return Partial(htmlHelper, partialViewName, null /* model */, viewData);
+ }
+
+ public static MvcHtmlString Partial(this HtmlHelper htmlHelper, string partialViewName, object model)
+ {
+ return Partial(htmlHelper, partialViewName, model, htmlHelper.ViewData);
+ }
+
+ public static MvcHtmlString Partial(this HtmlHelper htmlHelper, string partialViewName, object model, ViewDataDictionary viewData)
+ {
+ using (StringWriter writer = new StringWriter(CultureInfo.CurrentCulture))
+ {
+ htmlHelper.RenderPartialInternal(partialViewName, viewData, model, writer, ViewEngines.Engines);
+ return MvcHtmlString.Create(writer.ToString());
+ }
+ }
+ }
+}
diff --git a/src/System.Web.Mvc/Html/RenderPartialExtensions.cs b/src/System.Web.Mvc/Html/RenderPartialExtensions.cs
new file mode 100644
index 00000000..751c5dd9
--- /dev/null
+++ b/src/System.Web.Mvc/Html/RenderPartialExtensions.cs
@@ -0,0 +1,29 @@
+namespace System.Web.Mvc.Html
+{
+ public static class RenderPartialExtensions
+ {
+ // Renders the partial view with the parent's view data and model
+ public static void RenderPartial(this HtmlHelper htmlHelper, string partialViewName)
+ {
+ htmlHelper.RenderPartialInternal(partialViewName, htmlHelper.ViewData, null /* model */, htmlHelper.ViewContext.Writer, ViewEngines.Engines);
+ }
+
+ // Renders the partial view with the given view data and, implicitly, the given view data's model
+ public static void RenderPartial(this HtmlHelper htmlHelper, string partialViewName, ViewDataDictionary viewData)
+ {
+ htmlHelper.RenderPartialInternal(partialViewName, viewData, null /* model */, htmlHelper.ViewContext.Writer, ViewEngines.Engines);
+ }
+
+ // Renders the partial view with an empty view data and the given model
+ public static void RenderPartial(this HtmlHelper htmlHelper, string partialViewName, object model)
+ {
+ htmlHelper.RenderPartialInternal(partialViewName, htmlHelper.ViewData, model, htmlHelper.ViewContext.Writer, ViewEngines.Engines);
+ }
+
+ // Renders the partial view with a copy of the given view data plus the given model
+ public static void RenderPartial(this HtmlHelper htmlHelper, string partialViewName, object model, ViewDataDictionary viewData)
+ {
+ htmlHelper.RenderPartialInternal(partialViewName, viewData, model, htmlHelper.ViewContext.Writer, ViewEngines.Engines);
+ }
+ }
+}
diff --git a/src/System.Web.Mvc/Html/SelectExtensions.cs b/src/System.Web.Mvc/Html/SelectExtensions.cs
new file mode 100644
index 00000000..98a93f47
--- /dev/null
+++ b/src/System.Web.Mvc/Html/SelectExtensions.cs
@@ -0,0 +1,317 @@
+using System.Collections;
+using System.Collections.Generic;
+using System.Diagnostics.CodeAnalysis;
+using System.Globalization;
+using System.Linq;
+using System.Linq.Expressions;
+using System.Text;
+using System.Web.Mvc.Properties;
+
+namespace System.Web.Mvc.Html
+{
+ public static class SelectExtensions
+ {
+ // DropDownList
+
+ public static MvcHtmlString DropDownList(this HtmlHelper htmlHelper, string name)
+ {
+ return DropDownList(htmlHelper, name, null /* selectList */, null /* optionLabel */, null /* htmlAttributes */);
+ }
+
+ public static MvcHtmlString DropDownList(this HtmlHelper htmlHelper, string name, string optionLabel)
+ {
+ return DropDownList(htmlHelper, name, null /* selectList */, optionLabel, null /* htmlAttributes */);
+ }
+
+ public static MvcHtmlString DropDownList(this HtmlHelper htmlHelper, string name, IEnumerable<SelectListItem> selectList)
+ {
+ return DropDownList(htmlHelper, name, selectList, null /* optionLabel */, null /* htmlAttributes */);
+ }
+
+ public static MvcHtmlString DropDownList(this HtmlHelper htmlHelper, string name, IEnumerable<SelectListItem> selectList, object htmlAttributes)
+ {
+ return DropDownList(htmlHelper, name, selectList, null /* optionLabel */, HtmlHelper.AnonymousObjectToHtmlAttributes(htmlAttributes));
+ }
+
+ public static MvcHtmlString DropDownList(this HtmlHelper htmlHelper, string name, IEnumerable<SelectListItem> selectList, IDictionary<string, object> htmlAttributes)
+ {
+ return DropDownList(htmlHelper, name, selectList, null /* optionLabel */, htmlAttributes);
+ }
+
+ public static MvcHtmlString DropDownList(this HtmlHelper htmlHelper, string name, IEnumerable<SelectListItem> selectList, string optionLabel)
+ {
+ return DropDownList(htmlHelper, name, selectList, optionLabel, null /* htmlAttributes */);
+ }
+
+ public static MvcHtmlString DropDownList(this HtmlHelper htmlHelper, string name, IEnumerable<SelectListItem> selectList, string optionLabel, object htmlAttributes)
+ {
+ return DropDownList(htmlHelper, name, selectList, optionLabel, HtmlHelper.AnonymousObjectToHtmlAttributes(htmlAttributes));
+ }
+
+ public static MvcHtmlString DropDownList(this HtmlHelper htmlHelper, string name, IEnumerable<SelectListItem> selectList, string optionLabel, IDictionary<string, object> htmlAttributes)
+ {
+ return DropDownListHelper(htmlHelper, metadata: null, expression: name, selectList: selectList, optionLabel: optionLabel, htmlAttributes: htmlAttributes);
+ }
+
+ [SuppressMessage("Microsoft.Design", "CA1006:DoNotNestGenericTypesInMemberSignatures", Justification = "This is an appropriate nesting of generic types")]
+ public static MvcHtmlString DropDownListFor<TModel, TProperty>(this HtmlHelper<TModel> htmlHelper, Expression<Func<TModel, TProperty>> expression, IEnumerable<SelectListItem> selectList)
+ {
+ return DropDownListFor(htmlHelper, expression, selectList, null /* optionLabel */, null /* htmlAttributes */);
+ }
+
+ [SuppressMessage("Microsoft.Design", "CA1006:DoNotNestGenericTypesInMemberSignatures", Justification = "This is an appropriate nesting of generic types")]
+ public static MvcHtmlString DropDownListFor<TModel, TProperty>(this HtmlHelper<TModel> htmlHelper, Expression<Func<TModel, TProperty>> expression, IEnumerable<SelectListItem> selectList, object htmlAttributes)
+ {
+ return DropDownListFor(htmlHelper, expression, selectList, null /* optionLabel */, HtmlHelper.AnonymousObjectToHtmlAttributes(htmlAttributes));
+ }
+
+ [SuppressMessage("Microsoft.Design", "CA1006:DoNotNestGenericTypesInMemberSignatures", Justification = "This is an appropriate nesting of generic types")]
+ public static MvcHtmlString DropDownListFor<TModel, TProperty>(this HtmlHelper<TModel> htmlHelper, Expression<Func<TModel, TProperty>> expression, IEnumerable<SelectListItem> selectList, IDictionary<string, object> htmlAttributes)
+ {
+ return DropDownListFor(htmlHelper, expression, selectList, null /* optionLabel */, htmlAttributes);
+ }
+
+ [SuppressMessage("Microsoft.Design", "CA1006:DoNotNestGenericTypesInMemberSignatures", Justification = "This is an appropriate nesting of generic types")]
+ public static MvcHtmlString DropDownListFor<TModel, TProperty>(this HtmlHelper<TModel> htmlHelper, Expression<Func<TModel, TProperty>> expression, IEnumerable<SelectListItem> selectList, string optionLabel)
+ {
+ return DropDownListFor(htmlHelper, expression, selectList, optionLabel, null /* htmlAttributes */);
+ }
+
+ [SuppressMessage("Microsoft.Design", "CA1006:DoNotNestGenericTypesInMemberSignatures", Justification = "This is an appropriate nesting of generic types")]
+ public static MvcHtmlString DropDownListFor<TModel, TProperty>(this HtmlHelper<TModel> htmlHelper, Expression<Func<TModel, TProperty>> expression, IEnumerable<SelectListItem> selectList, string optionLabel, object htmlAttributes)
+ {
+ return DropDownListFor(htmlHelper, expression, selectList, optionLabel, HtmlHelper.AnonymousObjectToHtmlAttributes(htmlAttributes));
+ }
+
+ [SuppressMessage("Microsoft.Design", "CA1011:ConsiderPassingBaseTypesAsParameters", Justification = "Users cannot use anonymous methods with the LambdaExpression type")]
+ [SuppressMessage("Microsoft.Design", "CA1006:DoNotNestGenericTypesInMemberSignatures", Justification = "This is an appropriate nesting of generic types")]
+ public static MvcHtmlString DropDownListFor<TModel, TProperty>(this HtmlHelper<TModel> htmlHelper, Expression<Func<TModel, TProperty>> expression, IEnumerable<SelectListItem> selectList, string optionLabel, IDictionary<string, object> htmlAttributes)
+ {
+ if (expression == null)
+ {
+ throw new ArgumentNullException("expression");
+ }
+
+ ModelMetadata metadata = ModelMetadata.FromLambdaExpression(expression, htmlHelper.ViewData);
+
+ return DropDownListHelper(htmlHelper, metadata, ExpressionHelper.GetExpressionText(expression), selectList, optionLabel, htmlAttributes);
+ }
+
+ private static MvcHtmlString DropDownListHelper(HtmlHelper htmlHelper, ModelMetadata metadata, string expression, IEnumerable<SelectListItem> selectList, string optionLabel, IDictionary<string, object> htmlAttributes)
+ {
+ return SelectInternal(htmlHelper, metadata, optionLabel, expression, selectList, allowMultiple: false, htmlAttributes: htmlAttributes);
+ }
+
+ // ListBox
+
+ public static MvcHtmlString ListBox(this HtmlHelper htmlHelper, string name)
+ {
+ return ListBox(htmlHelper, name, null /* selectList */, null /* htmlAttributes */);
+ }
+
+ public static MvcHtmlString ListBox(this HtmlHelper htmlHelper, string name, IEnumerable<SelectListItem> selectList)
+ {
+ return ListBox(htmlHelper, name, selectList, (IDictionary<string, object>)null);
+ }
+
+ public static MvcHtmlString ListBox(this HtmlHelper htmlHelper, string name, IEnumerable<SelectListItem> selectList, object htmlAttributes)
+ {
+ return ListBox(htmlHelper, name, selectList, HtmlHelper.AnonymousObjectToHtmlAttributes(htmlAttributes));
+ }
+
+ public static MvcHtmlString ListBox(this HtmlHelper htmlHelper, string name, IEnumerable<SelectListItem> selectList, IDictionary<string, object> htmlAttributes)
+ {
+ return ListBoxHelper(htmlHelper, metadata: null, name: name, selectList: selectList, htmlAttributes: htmlAttributes);
+ }
+
+ [SuppressMessage("Microsoft.Design", "CA1006:DoNotNestGenericTypesInMemberSignatures", Justification = "This is an appropriate nesting of generic types")]
+ public static MvcHtmlString ListBoxFor<TModel, TProperty>(this HtmlHelper<TModel> htmlHelper, Expression<Func<TModel, TProperty>> expression, IEnumerable<SelectListItem> selectList)
+ {
+ return ListBoxFor(htmlHelper, expression, selectList, null /* htmlAttributes */);
+ }
+
+ [SuppressMessage("Microsoft.Design", "CA1006:DoNotNestGenericTypesInMemberSignatures", Justification = "This is an appropriate nesting of generic types")]
+ public static MvcHtmlString ListBoxFor<TModel, TProperty>(this HtmlHelper<TModel> htmlHelper, Expression<Func<TModel, TProperty>> expression, IEnumerable<SelectListItem> selectList, object htmlAttributes)
+ {
+ return ListBoxFor(htmlHelper, expression, selectList, HtmlHelper.AnonymousObjectToHtmlAttributes(htmlAttributes));
+ }
+
+ [SuppressMessage("Microsoft.Design", "CA1011:ConsiderPassingBaseTypesAsParameters", Justification = "Users cannot use anonymous methods with the LambdaExpression type")]
+ [SuppressMessage("Microsoft.Design", "CA1006:DoNotNestGenericTypesInMemberSignatures", Justification = "This is an appropriate nesting of generic types")]
+ public static MvcHtmlString ListBoxFor<TModel, TProperty>(this HtmlHelper<TModel> htmlHelper, Expression<Func<TModel, TProperty>> expression, IEnumerable<SelectListItem> selectList, IDictionary<string, object> htmlAttributes)
+ {
+ if (expression == null)
+ {
+ throw new ArgumentNullException("expression");
+ }
+
+ ModelMetadata metadata = ModelMetadata.FromLambdaExpression(expression, htmlHelper.ViewData);
+
+ return ListBoxHelper(htmlHelper,
+ metadata,
+ ExpressionHelper.GetExpressionText(expression),
+ selectList,
+ htmlAttributes);
+ }
+
+ private static MvcHtmlString ListBoxHelper(HtmlHelper htmlHelper, ModelMetadata metadata, string name, IEnumerable<SelectListItem> selectList, IDictionary<string, object> htmlAttributes)
+ {
+ return SelectInternal(htmlHelper, metadata, optionLabel: null, name: name, selectList: selectList, allowMultiple: true, htmlAttributes: htmlAttributes);
+ }
+
+ // Helper methods
+
+ private static IEnumerable<SelectListItem> GetSelectData(this HtmlHelper htmlHelper, string name)
+ {
+ object o = null;
+ if (htmlHelper.ViewData != null)
+ {
+ o = htmlHelper.ViewData.Eval(name);
+ }
+ if (o == null)
+ {
+ throw new InvalidOperationException(
+ String.Format(
+ CultureInfo.CurrentCulture,
+ MvcResources.HtmlHelper_MissingSelectData,
+ name,
+ "IEnumerable<SelectListItem>"));
+ }
+ IEnumerable<SelectListItem> selectList = o as IEnumerable<SelectListItem>;
+ if (selectList == null)
+ {
+ throw new InvalidOperationException(
+ String.Format(
+ CultureInfo.CurrentCulture,
+ MvcResources.HtmlHelper_WrongSelectDataType,
+ name,
+ o.GetType().FullName,
+ "IEnumerable<SelectListItem>"));
+ }
+ return selectList;
+ }
+
+ internal static string ListItemToOption(SelectListItem item)
+ {
+ TagBuilder builder = new TagBuilder("option")
+ {
+ InnerHtml = HttpUtility.HtmlEncode(item.Text)
+ };
+ if (item.Value != null)
+ {
+ builder.Attributes["value"] = item.Value;
+ }
+ if (item.Selected)
+ {
+ builder.Attributes["selected"] = "selected";
+ }
+ return builder.ToString(TagRenderMode.Normal);
+ }
+
+ private static IEnumerable<SelectListItem> GetSelectListWithDefaultValue(IEnumerable<SelectListItem> selectList, object defaultValue, bool allowMultiple)
+ {
+ IEnumerable defaultValues;
+
+ if (allowMultiple)
+ {
+ defaultValues = defaultValue as IEnumerable;
+ if (defaultValues == null || defaultValues is string)
+ {
+ throw new InvalidOperationException(
+ String.Format(
+ CultureInfo.CurrentCulture,
+ MvcResources.HtmlHelper_SelectExpressionNotEnumerable,
+ "expression"));
+ }
+ }
+ else
+ {
+ defaultValues = new[] { defaultValue };
+ }
+
+ IEnumerable<string> values = from object value in defaultValues
+ select Convert.ToString(value, CultureInfo.CurrentCulture);
+ HashSet<string> selectedValues = new HashSet<string>(values, StringComparer.OrdinalIgnoreCase);
+ List<SelectListItem> newSelectList = new List<SelectListItem>();
+
+ foreach (SelectListItem item in selectList)
+ {
+ item.Selected = (item.Value != null) ? selectedValues.Contains(item.Value) : selectedValues.Contains(item.Text);
+ newSelectList.Add(item);
+ }
+ return newSelectList;
+ }
+
+ private static MvcHtmlString SelectInternal(this HtmlHelper htmlHelper, ModelMetadata metadata, string optionLabel, string name, IEnumerable<SelectListItem> selectList, bool allowMultiple, IDictionary<string, object> htmlAttributes)
+ {
+ string fullName = htmlHelper.ViewContext.ViewData.TemplateInfo.GetFullHtmlFieldName(name);
+ if (String.IsNullOrEmpty(fullName))
+ {
+ throw new ArgumentException(MvcResources.Common_NullOrEmpty, "name");
+ }
+
+ bool usedViewData = false;
+
+ // If we got a null selectList, try to use ViewData to get the list of items.
+ if (selectList == null)
+ {
+ selectList = htmlHelper.GetSelectData(name);
+ usedViewData = true;
+ }
+
+ object defaultValue = (allowMultiple) ? htmlHelper.GetModelStateValue(fullName, typeof(string[])) : htmlHelper.GetModelStateValue(fullName, typeof(string));
+
+ // If we haven't already used ViewData to get the entire list of items then we need to
+ // use the ViewData-supplied value before using the parameter-supplied value.
+ if (!usedViewData && defaultValue == null && !String.IsNullOrEmpty(name))
+ {
+ defaultValue = htmlHelper.ViewData.Eval(name);
+ }
+
+ if (defaultValue != null)
+ {
+ selectList = GetSelectListWithDefaultValue(selectList, defaultValue, allowMultiple);
+ }
+
+ // Convert each ListItem to an <option> tag
+ StringBuilder listItemBuilder = new StringBuilder();
+
+ // Make optionLabel the first item that gets rendered.
+ if (optionLabel != null)
+ {
+ listItemBuilder.AppendLine(ListItemToOption(new SelectListItem() { Text = optionLabel, Value = String.Empty, Selected = false }));
+ }
+
+ foreach (SelectListItem item in selectList)
+ {
+ listItemBuilder.AppendLine(ListItemToOption(item));
+ }
+
+ TagBuilder tagBuilder = new TagBuilder("select")
+ {
+ InnerHtml = listItemBuilder.ToString()
+ };
+ tagBuilder.MergeAttributes(htmlAttributes);
+ tagBuilder.MergeAttribute("name", fullName, true /* replaceExisting */);
+ tagBuilder.GenerateId(fullName);
+ if (allowMultiple)
+ {
+ tagBuilder.MergeAttribute("multiple", "multiple");
+ }
+
+ // If there are any errors for a named field, we add the css attribute.
+ ModelState modelState;
+ if (htmlHelper.ViewData.ModelState.TryGetValue(fullName, out modelState))
+ {
+ if (modelState.Errors.Count > 0)
+ {
+ tagBuilder.AddCssClass(HtmlHelper.ValidationInputCssClassName);
+ }
+ }
+
+ tagBuilder.MergeAttributes(htmlHelper.GetUnobtrusiveValidationAttributes(name, metadata));
+
+ return tagBuilder.ToMvcHtmlString(TagRenderMode.Normal);
+ }
+ }
+}
diff --git a/src/System.Web.Mvc/Html/TemplateHelpers.cs b/src/System.Web.Mvc/Html/TemplateHelpers.cs
new file mode 100644
index 00000000..26290b8c
--- /dev/null
+++ b/src/System.Web.Mvc/Html/TemplateHelpers.cs
@@ -0,0 +1,333 @@
+using System.Collections;
+using System.Collections.Generic;
+using System.Globalization;
+using System.IO;
+using System.Linq;
+using System.Linq.Expressions;
+using System.Web.Mvc.Properties;
+using System.Web.Routing;
+using System.Web.UI.WebControls;
+
+namespace System.Web.Mvc.Html
+{
+ internal static class TemplateHelpers
+ {
+ private static readonly Dictionary<DataBoundControlMode, string> _modeViewPaths =
+ new Dictionary<DataBoundControlMode, string>
+ {
+ { DataBoundControlMode.ReadOnly, "DisplayTemplates" },
+ { DataBoundControlMode.Edit, "EditorTemplates" }
+ };
+
+ private static readonly Dictionary<string, Func<HtmlHelper, string>> _defaultDisplayActions =
+ new Dictionary<string, Func<HtmlHelper, string>>(StringComparer.OrdinalIgnoreCase)
+ {
+ { "EmailAddress", DefaultDisplayTemplates.EmailAddressTemplate },
+ { "HiddenInput", DefaultDisplayTemplates.HiddenInputTemplate },
+ { "Html", DefaultDisplayTemplates.HtmlTemplate },
+ { "Text", DefaultDisplayTemplates.StringTemplate },
+ { "Url", DefaultDisplayTemplates.UrlTemplate },
+ { "Collection", DefaultDisplayTemplates.CollectionTemplate },
+ { typeof(bool).Name, DefaultDisplayTemplates.BooleanTemplate },
+ { typeof(decimal).Name, DefaultDisplayTemplates.DecimalTemplate },
+ { typeof(string).Name, DefaultDisplayTemplates.StringTemplate },
+ { typeof(object).Name, DefaultDisplayTemplates.ObjectTemplate },
+ };
+
+ private static readonly Dictionary<string, Func<HtmlHelper, string>> _defaultEditorActions =
+ new Dictionary<string, Func<HtmlHelper, string>>(StringComparer.OrdinalIgnoreCase)
+ {
+ { "HiddenInput", DefaultEditorTemplates.HiddenInputTemplate },
+ { "MultilineText", DefaultEditorTemplates.MultilineTextTemplate },
+ { "Password", DefaultEditorTemplates.PasswordTemplate },
+ { "Text", DefaultEditorTemplates.StringTemplate },
+ { "Collection", DefaultEditorTemplates.CollectionTemplate },
+ { typeof(bool).Name, DefaultEditorTemplates.BooleanTemplate },
+ { typeof(decimal).Name, DefaultEditorTemplates.DecimalTemplate },
+ { typeof(string).Name, DefaultEditorTemplates.StringTemplate },
+ { typeof(object).Name, DefaultEditorTemplates.ObjectTemplate },
+ };
+
+ internal static string CacheItemId = Guid.NewGuid().ToString();
+
+ internal delegate string ExecuteTemplateDelegate(HtmlHelper html, ViewDataDictionary viewData, string templateName,
+ DataBoundControlMode mode, GetViewNamesDelegate getViewNames,
+ GetDefaultActionsDelegate getDefaultActions);
+
+ internal delegate Dictionary<string, Func<HtmlHelper, string>> GetDefaultActionsDelegate(DataBoundControlMode mode);
+
+ internal delegate IEnumerable<string> GetViewNamesDelegate(ModelMetadata metadata, params string[] templateHints);
+
+ internal delegate string TemplateHelperDelegate(HtmlHelper html, ModelMetadata metadata, string htmlFieldName,
+ string templateName, DataBoundControlMode mode, object additionalViewData);
+
+ internal static string ExecuteTemplate(HtmlHelper html, ViewDataDictionary viewData, string templateName, DataBoundControlMode mode, GetViewNamesDelegate getViewNames, GetDefaultActionsDelegate getDefaultActions)
+ {
+ Dictionary<string, ActionCacheItem> actionCache = GetActionCache(html);
+ Dictionary<string, Func<HtmlHelper, string>> defaultActions = getDefaultActions(mode);
+ string modeViewPath = _modeViewPaths[mode];
+
+ foreach (string viewName in getViewNames(viewData.ModelMetadata, templateName, viewData.ModelMetadata.TemplateHint, viewData.ModelMetadata.DataTypeName))
+ {
+ string fullViewName = modeViewPath + "/" + viewName;
+ ActionCacheItem cacheItem;
+
+ if (actionCache.TryGetValue(fullViewName, out cacheItem))
+ {
+ if (cacheItem != null)
+ {
+ return cacheItem.Execute(html, viewData);
+ }
+ }
+ else
+ {
+ ViewEngineResult viewEngineResult = ViewEngines.Engines.FindPartialView(html.ViewContext, fullViewName);
+ if (viewEngineResult.View != null)
+ {
+ actionCache[fullViewName] = new ActionCacheViewItem { ViewName = fullViewName };
+
+ using (StringWriter writer = new StringWriter(CultureInfo.InvariantCulture))
+ {
+ viewEngineResult.View.Render(new ViewContext(html.ViewContext, viewEngineResult.View, viewData, html.ViewContext.TempData, writer), writer);
+ return writer.ToString();
+ }
+ }
+
+ Func<HtmlHelper, string> defaultAction;
+ if (defaultActions.TryGetValue(viewName, out defaultAction))
+ {
+ actionCache[fullViewName] = new ActionCacheCodeItem { Action = defaultAction };
+ return defaultAction(MakeHtmlHelper(html, viewData));
+ }
+
+ actionCache[fullViewName] = null;
+ }
+ }
+
+ throw new InvalidOperationException(
+ String.Format(
+ CultureInfo.CurrentCulture,
+ MvcResources.TemplateHelpers_NoTemplate,
+ viewData.ModelMetadata.RealModelType.FullName));
+ }
+
+ internal static Dictionary<string, ActionCacheItem> GetActionCache(HtmlHelper html)
+ {
+ HttpContextBase context = html.ViewContext.HttpContext;
+ Dictionary<string, ActionCacheItem> result;
+
+ if (!context.Items.Contains(CacheItemId))
+ {
+ result = new Dictionary<string, ActionCacheItem>();
+ context.Items[CacheItemId] = result;
+ }
+ else
+ {
+ result = (Dictionary<string, ActionCacheItem>)context.Items[CacheItemId];
+ }
+
+ return result;
+ }
+
+ internal static Dictionary<string, Func<HtmlHelper, string>> GetDefaultActions(DataBoundControlMode mode)
+ {
+ return mode == DataBoundControlMode.ReadOnly ? _defaultDisplayActions : _defaultEditorActions;
+ }
+
+ internal static IEnumerable<string> GetViewNames(ModelMetadata metadata, params string[] templateHints)
+ {
+ foreach (string templateHint in templateHints.Where(s => !String.IsNullOrEmpty(s)))
+ {
+ yield return templateHint;
+ }
+
+ // We don't want to search for Nullable<T>, we want to search for T (which should handle both T and Nullable<T>)
+ Type fieldType = Nullable.GetUnderlyingType(metadata.RealModelType) ?? metadata.RealModelType;
+
+ // TODO: Make better string names for generic types
+ yield return fieldType.Name;
+
+ if (!metadata.IsComplexType)
+ {
+ yield return "String";
+ }
+ else if (fieldType.IsInterface)
+ {
+ if (typeof(IEnumerable).IsAssignableFrom(fieldType))
+ {
+ yield return "Collection";
+ }
+
+ yield return "Object";
+ }
+ else
+ {
+ bool isEnumerable = typeof(IEnumerable).IsAssignableFrom(fieldType);
+
+ while (true)
+ {
+ fieldType = fieldType.BaseType;
+ if (fieldType == null)
+ {
+ break;
+ }
+
+ if (isEnumerable && fieldType == typeof(Object))
+ {
+ yield return "Collection";
+ }
+
+ yield return fieldType.Name;
+ }
+ }
+ }
+
+ internal static MvcHtmlString Template(HtmlHelper html, string expression, string templateName, string htmlFieldName, DataBoundControlMode mode, object additionalViewData)
+ {
+ return MvcHtmlString.Create(Template(html, expression, templateName, htmlFieldName, mode, additionalViewData, TemplateHelper));
+ }
+
+ // Unit testing version
+ internal static string Template(HtmlHelper html, string expression, string templateName, string htmlFieldName,
+ DataBoundControlMode mode, object additionalViewData, TemplateHelperDelegate templateHelper)
+ {
+ return templateHelper(html,
+ ModelMetadata.FromStringExpression(expression, html.ViewData),
+ htmlFieldName ?? ExpressionHelper.GetExpressionText(expression),
+ templateName,
+ mode,
+ additionalViewData);
+ }
+
+ internal static MvcHtmlString TemplateFor<TContainer, TValue>(this HtmlHelper<TContainer> html, Expression<Func<TContainer, TValue>> expression,
+ string templateName, string htmlFieldName, DataBoundControlMode mode,
+ object additionalViewData)
+ {
+ return MvcHtmlString.Create(TemplateFor(html, expression, templateName, htmlFieldName, mode, additionalViewData, TemplateHelper));
+ }
+
+ // Unit testing version
+ internal static string TemplateFor<TContainer, TValue>(this HtmlHelper<TContainer> html, Expression<Func<TContainer, TValue>> expression,
+ string templateName, string htmlFieldName, DataBoundControlMode mode,
+ object additionalViewData, TemplateHelperDelegate templateHelper)
+ {
+ return templateHelper(html,
+ ModelMetadata.FromLambdaExpression(expression, html.ViewData),
+ htmlFieldName ?? ExpressionHelper.GetExpressionText(expression),
+ templateName,
+ mode,
+ additionalViewData);
+ }
+
+ internal static string TemplateHelper(HtmlHelper html, ModelMetadata metadata, string htmlFieldName, string templateName, DataBoundControlMode mode, object additionalViewData)
+ {
+ return TemplateHelper(html, metadata, htmlFieldName, templateName, mode, additionalViewData, ExecuteTemplate);
+ }
+
+ internal static string TemplateHelper(HtmlHelper html, ModelMetadata metadata, string htmlFieldName, string templateName, DataBoundControlMode mode, object additionalViewData, ExecuteTemplateDelegate executeTemplate)
+ {
+ // TODO: Convert Editor into Display if model.IsReadOnly is true? Need to be careful about this because
+ // the Model property on the ViewPage/ViewUserControl is get-only, so the type descriptor automatically
+ // decorates it with a [ReadOnly] attribute...
+
+ if (metadata.ConvertEmptyStringToNull && String.Empty.Equals(metadata.Model))
+ {
+ metadata.Model = null;
+ }
+
+ object formattedModelValue = metadata.Model;
+ if (metadata.Model == null && mode == DataBoundControlMode.ReadOnly)
+ {
+ formattedModelValue = metadata.NullDisplayText;
+ }
+
+ string formatString = mode == DataBoundControlMode.ReadOnly ? metadata.DisplayFormatString : metadata.EditFormatString;
+ if (metadata.Model != null && !String.IsNullOrEmpty(formatString))
+ {
+ formattedModelValue = String.Format(CultureInfo.CurrentCulture, formatString, metadata.Model);
+ }
+
+ // Normally this shouldn't happen, unless someone writes their own custom Object templates which
+ // don't check to make sure that the object hasn't already been displayed
+ object visitedObjectsKey = metadata.Model ?? metadata.RealModelType;
+ if (html.ViewDataContainer.ViewData.TemplateInfo.VisitedObjects.Contains(visitedObjectsKey))
+ {
+ // DDB #224750
+ return String.Empty;
+ }
+
+ ViewDataDictionary viewData = new ViewDataDictionary(html.ViewDataContainer.ViewData)
+ {
+ Model = metadata.Model,
+ ModelMetadata = metadata,
+ TemplateInfo = new TemplateInfo
+ {
+ FormattedModelValue = formattedModelValue,
+ HtmlFieldPrefix = html.ViewContext.ViewData.TemplateInfo.GetFullHtmlFieldName(htmlFieldName),
+ VisitedObjects = new HashSet<object>(html.ViewContext.ViewData.TemplateInfo.VisitedObjects), // DDB #224750
+ }
+ };
+
+ if (additionalViewData != null)
+ {
+ foreach (KeyValuePair<string, object> kvp in new RouteValueDictionary(additionalViewData))
+ {
+ viewData[kvp.Key] = kvp.Value;
+ }
+ }
+
+ viewData.TemplateInfo.VisitedObjects.Add(visitedObjectsKey); // DDB #224750
+
+ return executeTemplate(html, viewData, templateName, mode, GetViewNames, GetDefaultActions);
+ }
+
+ // Helpers
+
+ private static HtmlHelper MakeHtmlHelper(HtmlHelper html, ViewDataDictionary viewData)
+ {
+ return new HtmlHelper(
+ new ViewContext(html.ViewContext, html.ViewContext.View, viewData, html.ViewContext.TempData, html.ViewContext.Writer),
+ new ViewDataContainer(viewData));
+ }
+
+ internal class ActionCacheCodeItem : ActionCacheItem
+ {
+ public Func<HtmlHelper, string> Action { get; set; }
+
+ public override string Execute(HtmlHelper html, ViewDataDictionary viewData)
+ {
+ return Action(MakeHtmlHelper(html, viewData));
+ }
+ }
+
+ internal abstract class ActionCacheItem
+ {
+ public abstract string Execute(HtmlHelper html, ViewDataDictionary viewData);
+ }
+
+ internal class ActionCacheViewItem : ActionCacheItem
+ {
+ public string ViewName { get; set; }
+
+ public override string Execute(HtmlHelper html, ViewDataDictionary viewData)
+ {
+ ViewEngineResult viewEngineResult = ViewEngines.Engines.FindPartialView(html.ViewContext, ViewName);
+ using (StringWriter writer = new StringWriter(CultureInfo.InvariantCulture))
+ {
+ viewEngineResult.View.Render(new ViewContext(html.ViewContext, viewEngineResult.View, viewData, html.ViewContext.TempData, writer), writer);
+ return writer.ToString();
+ }
+ }
+ }
+
+ private class ViewDataContainer : IViewDataContainer
+ {
+ public ViewDataContainer(ViewDataDictionary viewData)
+ {
+ ViewData = viewData;
+ }
+
+ public ViewDataDictionary ViewData { get; set; }
+ }
+ }
+}
diff --git a/src/System.Web.Mvc/Html/TextAreaExtensions.cs b/src/System.Web.Mvc/Html/TextAreaExtensions.cs
new file mode 100644
index 00000000..490a76e7
--- /dev/null
+++ b/src/System.Web.Mvc/Html/TextAreaExtensions.cs
@@ -0,0 +1,192 @@
+using System.Collections.Generic;
+using System.Diagnostics.CodeAnalysis;
+using System.Globalization;
+using System.Linq.Expressions;
+using System.Web.Mvc.Properties;
+
+namespace System.Web.Mvc.Html
+{
+ public static class TextAreaExtensions
+ {
+ // These values are similar to the defaults used by WebForms
+ // when using <asp:TextBox TextMode="MultiLine"> without specifying
+ // the Rows and Columns attributes.
+ private const int TextAreaRows = 2;
+ private const int TextAreaColumns = 20;
+
+ private static Dictionary<string, object> implicitRowsAndColumns = new Dictionary<string, object>
+ {
+ { "rows", TextAreaRows.ToString(CultureInfo.InvariantCulture) },
+ { "cols", TextAreaColumns.ToString(CultureInfo.InvariantCulture) },
+ };
+
+ private static Dictionary<string, object> GetRowsAndColumnsDictionary(int rows, int columns)
+ {
+ if (rows < 0)
+ {
+ throw new ArgumentOutOfRangeException("rows", MvcResources.HtmlHelper_TextAreaParameterOutOfRange);
+ }
+ if (columns < 0)
+ {
+ throw new ArgumentOutOfRangeException("columns", MvcResources.HtmlHelper_TextAreaParameterOutOfRange);
+ }
+
+ Dictionary<string, object> result = new Dictionary<string, object>();
+ if (rows > 0)
+ {
+ result.Add("rows", rows.ToString(CultureInfo.InvariantCulture));
+ }
+ if (columns > 0)
+ {
+ result.Add("cols", columns.ToString(CultureInfo.InvariantCulture));
+ }
+
+ return result;
+ }
+
+ public static MvcHtmlString TextArea(this HtmlHelper htmlHelper, string name)
+ {
+ return TextArea(htmlHelper, name, null /* value */, null /* htmlAttributes */);
+ }
+
+ public static MvcHtmlString TextArea(this HtmlHelper htmlHelper, string name, object htmlAttributes)
+ {
+ return TextArea(htmlHelper, name, null /* value */, HtmlHelper.AnonymousObjectToHtmlAttributes(htmlAttributes));
+ }
+
+ public static MvcHtmlString TextArea(this HtmlHelper htmlHelper, string name, IDictionary<string, object> htmlAttributes)
+ {
+ return TextArea(htmlHelper, name, null /* value */, htmlAttributes);
+ }
+
+ public static MvcHtmlString TextArea(this HtmlHelper htmlHelper, string name, string value)
+ {
+ return TextArea(htmlHelper, name, value, null /* htmlAttributes */);
+ }
+
+ public static MvcHtmlString TextArea(this HtmlHelper htmlHelper, string name, string value, object htmlAttributes)
+ {
+ return TextArea(htmlHelper, name, value, HtmlHelper.AnonymousObjectToHtmlAttributes(htmlAttributes));
+ }
+
+ public static MvcHtmlString TextArea(this HtmlHelper htmlHelper, string name, string value, IDictionary<string, object> htmlAttributes)
+ {
+ ModelMetadata metadata = ModelMetadata.FromStringExpression(name, htmlHelper.ViewContext.ViewData);
+ if (value != null)
+ {
+ metadata.Model = value;
+ }
+
+ return TextAreaHelper(htmlHelper, metadata, name, implicitRowsAndColumns, htmlAttributes);
+ }
+
+ public static MvcHtmlString TextArea(this HtmlHelper htmlHelper, string name, string value, int rows, int columns, object htmlAttributes)
+ {
+ return TextArea(htmlHelper, name, value, rows, columns, HtmlHelper.AnonymousObjectToHtmlAttributes(htmlAttributes));
+ }
+
+ public static MvcHtmlString TextArea(this HtmlHelper htmlHelper, string name, string value, int rows, int columns, IDictionary<string, object> htmlAttributes)
+ {
+ ModelMetadata metadata = ModelMetadata.FromStringExpression(name, htmlHelper.ViewContext.ViewData);
+ if (value != null)
+ {
+ metadata.Model = value;
+ }
+
+ return TextAreaHelper(htmlHelper, metadata, name, GetRowsAndColumnsDictionary(rows, columns), htmlAttributes);
+ }
+
+ [SuppressMessage("Microsoft.Design", "CA1006:DoNotNestGenericTypesInMemberSignatures", Justification = "This is an appropriate nesting of generic types")]
+ public static MvcHtmlString TextAreaFor<TModel, TProperty>(this HtmlHelper<TModel> htmlHelper, Expression<Func<TModel, TProperty>> expression)
+ {
+ return TextAreaFor(htmlHelper, expression, (IDictionary<string, object>)null);
+ }
+
+ [SuppressMessage("Microsoft.Design", "CA1006:DoNotNestGenericTypesInMemberSignatures", Justification = "This is an appropriate nesting of generic types")]
+ public static MvcHtmlString TextAreaFor<TModel, TProperty>(this HtmlHelper<TModel> htmlHelper, Expression<Func<TModel, TProperty>> expression, object htmlAttributes)
+ {
+ return TextAreaFor(htmlHelper, expression, HtmlHelper.AnonymousObjectToHtmlAttributes(htmlAttributes));
+ }
+
+ [SuppressMessage("Microsoft.Design", "CA1006:DoNotNestGenericTypesInMemberSignatures", Justification = "This is an appropriate nesting of generic types")]
+ public static MvcHtmlString TextAreaFor<TModel, TProperty>(this HtmlHelper<TModel> htmlHelper, Expression<Func<TModel, TProperty>> expression, IDictionary<string, object> htmlAttributes)
+ {
+ if (expression == null)
+ {
+ throw new ArgumentNullException("expression");
+ }
+
+ return TextAreaHelper(htmlHelper,
+ ModelMetadata.FromLambdaExpression(expression, htmlHelper.ViewData),
+ ExpressionHelper.GetExpressionText(expression),
+ implicitRowsAndColumns,
+ htmlAttributes);
+ }
+
+ [SuppressMessage("Microsoft.Design", "CA1006:DoNotNestGenericTypesInMemberSignatures", Justification = "This is an appropriate nesting of generic types")]
+ public static MvcHtmlString TextAreaFor<TModel, TProperty>(this HtmlHelper<TModel> htmlHelper, Expression<Func<TModel, TProperty>> expression, int rows, int columns, object htmlAttributes)
+ {
+ return TextAreaFor(htmlHelper, expression, rows, columns, HtmlHelper.AnonymousObjectToHtmlAttributes(htmlAttributes));
+ }
+
+ [SuppressMessage("Microsoft.Design", "CA1006:DoNotNestGenericTypesInMemberSignatures", Justification = "This is an appropriate nesting of generic types")]
+ public static MvcHtmlString TextAreaFor<TModel, TProperty>(this HtmlHelper<TModel> htmlHelper, Expression<Func<TModel, TProperty>> expression, int rows, int columns, IDictionary<string, object> htmlAttributes)
+ {
+ if (expression == null)
+ {
+ throw new ArgumentNullException("expression");
+ }
+
+ return TextAreaHelper(htmlHelper,
+ ModelMetadata.FromLambdaExpression(expression, htmlHelper.ViewData),
+ ExpressionHelper.GetExpressionText(expression),
+ GetRowsAndColumnsDictionary(rows, columns),
+ htmlAttributes);
+ }
+
+ [SuppressMessage("Microsoft.Usage", "CA2208:InstantiateArgumentExceptionsCorrectly", Justification = "If this fails, it is because the string-based version had an empty 'name' parameter")]
+ internal static MvcHtmlString TextAreaHelper(HtmlHelper htmlHelper, ModelMetadata modelMetadata, string name, IDictionary<string, object> rowsAndColumns, IDictionary<string, object> htmlAttributes, string innerHtmlPrefix = null)
+ {
+ string fullName = htmlHelper.ViewContext.ViewData.TemplateInfo.GetFullHtmlFieldName(name);
+ if (String.IsNullOrEmpty(fullName))
+ {
+ throw new ArgumentException(MvcResources.Common_NullOrEmpty, "name");
+ }
+
+ TagBuilder tagBuilder = new TagBuilder("textarea");
+ tagBuilder.GenerateId(fullName);
+ tagBuilder.MergeAttributes(htmlAttributes, true);
+ tagBuilder.MergeAttributes(rowsAndColumns, rowsAndColumns != implicitRowsAndColumns); // Only force explicit rows/cols
+ tagBuilder.MergeAttribute("name", fullName, true);
+
+ // If there are any errors for a named field, we add the CSS attribute.
+ ModelState modelState;
+ if (htmlHelper.ViewData.ModelState.TryGetValue(fullName, out modelState) && modelState.Errors.Count > 0)
+ {
+ tagBuilder.AddCssClass(HtmlHelper.ValidationInputCssClassName);
+ }
+
+ tagBuilder.MergeAttributes(htmlHelper.GetUnobtrusiveValidationAttributes(name));
+
+ string value;
+ if (modelState != null && modelState.Value != null)
+ {
+ value = modelState.Value.AttemptedValue;
+ }
+ else if (modelMetadata.Model != null)
+ {
+ value = modelMetadata.Model.ToString();
+ }
+ else
+ {
+ value = String.Empty;
+ }
+
+ // The first newline is always trimmed when a TextArea is rendered, so we add an extra one
+ // in case the value being rendered is something like "\r\nHello".
+ tagBuilder.InnerHtml = (innerHtmlPrefix ?? Environment.NewLine) + HttpUtility.HtmlEncode(value);
+
+ return tagBuilder.ToMvcHtmlString(TagRenderMode.Normal);
+ }
+ }
+}
diff --git a/src/System.Web.Mvc/Html/ValidationExtensions.cs b/src/System.Web.Mvc/Html/ValidationExtensions.cs
new file mode 100644
index 00000000..b225110e
--- /dev/null
+++ b/src/System.Web.Mvc/Html/ValidationExtensions.cs
@@ -0,0 +1,390 @@
+using System.Collections.Generic;
+using System.Diagnostics.CodeAnalysis;
+using System.Globalization;
+using System.Linq;
+using System.Linq.Expressions;
+using System.Text;
+using System.Web.Mvc.Properties;
+using System.Web.Routing;
+
+namespace System.Web.Mvc.Html
+{
+ public static class ValidationExtensions
+ {
+ private const string HiddenListItem = @"<li style=""display:none""></li>";
+ private static string _resourceClassKey;
+
+ public static string ResourceClassKey
+ {
+ get { return _resourceClassKey ?? String.Empty; }
+ set { _resourceClassKey = value; }
+ }
+
+ private static FieldValidationMetadata ApplyFieldValidationMetadata(HtmlHelper htmlHelper, ModelMetadata modelMetadata, string modelName)
+ {
+ FormContext formContext = htmlHelper.ViewContext.FormContext;
+ FieldValidationMetadata fieldMetadata = formContext.GetValidationMetadataForField(modelName, true /* createIfNotFound */);
+
+ // write rules to context object
+ IEnumerable<ModelValidator> validators = ModelValidatorProviders.Providers.GetValidators(modelMetadata, htmlHelper.ViewContext);
+ foreach (ModelClientValidationRule rule in validators.SelectMany(v => v.GetClientValidationRules()))
+ {
+ fieldMetadata.ValidationRules.Add(rule);
+ }
+
+ return fieldMetadata;
+ }
+
+ private static string GetInvalidPropertyValueResource(HttpContextBase httpContext)
+ {
+ string resourceValue = null;
+ if (!String.IsNullOrEmpty(ResourceClassKey) && (httpContext != null))
+ {
+ // If the user specified a ResourceClassKey try to load the resource they specified.
+ // If the class key is invalid, an exception will be thrown.
+ // If the class key is valid but the resource is not found, it returns null, in which
+ // case it will fall back to the MVC default error message.
+ resourceValue = httpContext.GetGlobalResourceObject(ResourceClassKey, "InvalidPropertyValue", CultureInfo.CurrentUICulture) as string;
+ }
+ return resourceValue ?? MvcResources.Common_ValueNotValidForProperty;
+ }
+
+ private static string GetUserErrorMessageOrDefault(HttpContextBase httpContext, ModelError error, ModelState modelState)
+ {
+ if (!String.IsNullOrEmpty(error.ErrorMessage))
+ {
+ return error.ErrorMessage;
+ }
+ if (modelState == null)
+ {
+ return null;
+ }
+
+ string attemptedValue = (modelState.Value != null) ? modelState.Value.AttemptedValue : null;
+ return String.Format(CultureInfo.CurrentCulture, GetInvalidPropertyValueResource(httpContext), attemptedValue);
+ }
+
+ // Validate
+
+ public static void Validate(this HtmlHelper htmlHelper, string modelName)
+ {
+ if (modelName == null)
+ {
+ throw new ArgumentNullException("modelName");
+ }
+
+ ValidateHelper(htmlHelper,
+ ModelMetadata.FromStringExpression(modelName, htmlHelper.ViewContext.ViewData),
+ modelName);
+ }
+
+ [SuppressMessage("Microsoft.Design", "CA1006:DoNotNestGenericTypesInMemberSignatures", Justification = "This is an appropriate nesting of generic types")]
+ public static void ValidateFor<TModel, TProperty>(this HtmlHelper<TModel> htmlHelper, Expression<Func<TModel, TProperty>> expression)
+ {
+ ValidateHelper(htmlHelper,
+ ModelMetadata.FromLambdaExpression(expression, htmlHelper.ViewData),
+ ExpressionHelper.GetExpressionText(expression));
+ }
+
+ private static void ValidateHelper(HtmlHelper htmlHelper, ModelMetadata modelMetadata, string expression)
+ {
+ FormContext formContext = htmlHelper.ViewContext.GetFormContextForClientValidation();
+ if (formContext == null || htmlHelper.ViewContext.UnobtrusiveJavaScriptEnabled)
+ {
+ return; // nothing to do
+ }
+
+ string modelName = htmlHelper.ViewContext.ViewData.TemplateInfo.GetFullHtmlFieldName(expression);
+ ApplyFieldValidationMetadata(htmlHelper, modelMetadata, modelName);
+ }
+
+ // ValidationMessage
+
+ public static MvcHtmlString ValidationMessage(this HtmlHelper htmlHelper, string modelName)
+ {
+ return ValidationMessage(htmlHelper, modelName, null /* validationMessage */, new RouteValueDictionary());
+ }
+
+ public static MvcHtmlString ValidationMessage(this HtmlHelper htmlHelper, string modelName, object htmlAttributes)
+ {
+ return ValidationMessage(htmlHelper, modelName, null /* validationMessage */, HtmlHelper.AnonymousObjectToHtmlAttributes(htmlAttributes));
+ }
+
+ [SuppressMessage("Microsoft.Naming", "CA1719:ParameterNamesShouldNotMatchMemberNames", Justification = "'validationMessage' refers to the message that will be rendered by the ValidationMessage helper.")]
+ public static MvcHtmlString ValidationMessage(this HtmlHelper htmlHelper, string modelName, string validationMessage)
+ {
+ return ValidationMessage(htmlHelper, modelName, validationMessage, new RouteValueDictionary());
+ }
+
+ [SuppressMessage("Microsoft.Naming", "CA1719:ParameterNamesShouldNotMatchMemberNames", Justification = "'validationMessage' refers to the message that will be rendered by the ValidationMessage helper.")]
+ public static MvcHtmlString ValidationMessage(this HtmlHelper htmlHelper, string modelName, string validationMessage, object htmlAttributes)
+ {
+ return ValidationMessage(htmlHelper, modelName, validationMessage, HtmlHelper.AnonymousObjectToHtmlAttributes(htmlAttributes));
+ }
+
+ public static MvcHtmlString ValidationMessage(this HtmlHelper htmlHelper, string modelName, IDictionary<string, object> htmlAttributes)
+ {
+ return ValidationMessage(htmlHelper, modelName, null /* validationMessage */, htmlAttributes);
+ }
+
+ [SuppressMessage("Microsoft.Naming", "CA1719:ParameterNamesShouldNotMatchMemberNames", Justification = "'validationMessage' refers to the message that will be rendered by the ValidationMessage helper.")]
+ public static MvcHtmlString ValidationMessage(this HtmlHelper htmlHelper, string modelName, string validationMessage, IDictionary<string, object> htmlAttributes)
+ {
+ if (modelName == null)
+ {
+ throw new ArgumentNullException("modelName");
+ }
+
+ return ValidationMessageHelper(htmlHelper,
+ ModelMetadata.FromStringExpression(modelName, htmlHelper.ViewContext.ViewData),
+ modelName,
+ validationMessage,
+ htmlAttributes);
+ }
+
+ [SuppressMessage("Microsoft.Design", "CA1006:DoNotNestGenericTypesInMemberSignatures", Justification = "This is an appropriate nesting of generic types")]
+ public static MvcHtmlString ValidationMessageFor<TModel, TProperty>(this HtmlHelper<TModel> htmlHelper, Expression<Func<TModel, TProperty>> expression)
+ {
+ return ValidationMessageFor(htmlHelper, expression, null /* validationMessage */, new RouteValueDictionary());
+ }
+
+ [SuppressMessage("Microsoft.Design", "CA1006:DoNotNestGenericTypesInMemberSignatures", Justification = "This is an appropriate nesting of generic types")]
+ public static MvcHtmlString ValidationMessageFor<TModel, TProperty>(this HtmlHelper<TModel> htmlHelper, Expression<Func<TModel, TProperty>> expression, string validationMessage)
+ {
+ return ValidationMessageFor(htmlHelper, expression, validationMessage, new RouteValueDictionary());
+ }
+
+ [SuppressMessage("Microsoft.Design", "CA1006:DoNotNestGenericTypesInMemberSignatures", Justification = "This is an appropriate nesting of generic types")]
+ public static MvcHtmlString ValidationMessageFor<TModel, TProperty>(this HtmlHelper<TModel> htmlHelper, Expression<Func<TModel, TProperty>> expression, string validationMessage, object htmlAttributes)
+ {
+ return ValidationMessageFor(htmlHelper, expression, validationMessage, HtmlHelper.AnonymousObjectToHtmlAttributes(htmlAttributes));
+ }
+
+ [SuppressMessage("Microsoft.Design", "CA1006:DoNotNestGenericTypesInMemberSignatures", Justification = "This is an appropriate nesting of generic types")]
+ public static MvcHtmlString ValidationMessageFor<TModel, TProperty>(this HtmlHelper<TModel> htmlHelper, Expression<Func<TModel, TProperty>> expression, string validationMessage, IDictionary<string, object> htmlAttributes)
+ {
+ return ValidationMessageHelper(htmlHelper,
+ ModelMetadata.FromLambdaExpression(expression, htmlHelper.ViewData),
+ ExpressionHelper.GetExpressionText(expression),
+ validationMessage,
+ htmlAttributes);
+ }
+
+ [SuppressMessage("Microsoft.Globalization", "CA1308:NormalizeStringsToUppercase", Justification = "Normalization to lowercase is a common requirement for JavaScript and HTML values")]
+ private static MvcHtmlString ValidationMessageHelper(this HtmlHelper htmlHelper, ModelMetadata modelMetadata, string expression, string validationMessage, IDictionary<string, object> htmlAttributes)
+ {
+ string modelName = htmlHelper.ViewContext.ViewData.TemplateInfo.GetFullHtmlFieldName(expression);
+ FormContext formContext = htmlHelper.ViewContext.GetFormContextForClientValidation();
+
+ if (!htmlHelper.ViewData.ModelState.ContainsKey(modelName) && formContext == null)
+ {
+ return null;
+ }
+
+ ModelState modelState = htmlHelper.ViewData.ModelState[modelName];
+ ModelErrorCollection modelErrors = (modelState == null) ? null : modelState.Errors;
+ ModelError modelError = (((modelErrors == null) || (modelErrors.Count == 0)) ? null : modelErrors.FirstOrDefault(m => !String.IsNullOrEmpty(m.ErrorMessage)) ?? modelErrors[0]);
+
+ if (modelError == null && formContext == null)
+ {
+ return null;
+ }
+
+ TagBuilder builder = new TagBuilder("span");
+ builder.MergeAttributes(htmlAttributes);
+ builder.AddCssClass((modelError != null) ? HtmlHelper.ValidationMessageCssClassName : HtmlHelper.ValidationMessageValidCssClassName);
+
+ if (!String.IsNullOrEmpty(validationMessage))
+ {
+ builder.SetInnerText(validationMessage);
+ }
+ else if (modelError != null)
+ {
+ builder.SetInnerText(GetUserErrorMessageOrDefault(htmlHelper.ViewContext.HttpContext, modelError, modelState));
+ }
+
+ if (formContext != null)
+ {
+ bool replaceValidationMessageContents = String.IsNullOrEmpty(validationMessage);
+
+ if (htmlHelper.ViewContext.UnobtrusiveJavaScriptEnabled)
+ {
+ builder.MergeAttribute("data-valmsg-for", modelName);
+ builder.MergeAttribute("data-valmsg-replace", replaceValidationMessageContents.ToString().ToLowerInvariant());
+ }
+ else
+ {
+ FieldValidationMetadata fieldMetadata = ApplyFieldValidationMetadata(htmlHelper, modelMetadata, modelName);
+ // rules will already have been written to the metadata object
+ fieldMetadata.ReplaceValidationMessageContents = replaceValidationMessageContents; // only replace contents if no explicit message was specified
+
+ // client validation always requires an ID
+ builder.GenerateId(modelName + "_validationMessage");
+ fieldMetadata.ValidationMessageId = builder.Attributes["id"];
+ }
+ }
+
+ return builder.ToMvcHtmlString(TagRenderMode.Normal);
+ }
+
+ // ValidationSummary
+
+ public static MvcHtmlString ValidationSummary(this HtmlHelper htmlHelper)
+ {
+ return ValidationSummary(htmlHelper, false /* excludePropertyErrors */);
+ }
+
+ public static MvcHtmlString ValidationSummary(this HtmlHelper htmlHelper, bool excludePropertyErrors)
+ {
+ return ValidationSummary(htmlHelper, excludePropertyErrors, null /* message */);
+ }
+
+ public static MvcHtmlString ValidationSummary(this HtmlHelper htmlHelper, string message)
+ {
+ return ValidationSummary(htmlHelper, false /* excludePropertyErrors */, message, (object)null /* htmlAttributes */);
+ }
+
+ public static MvcHtmlString ValidationSummary(this HtmlHelper htmlHelper, bool excludePropertyErrors, string message)
+ {
+ return ValidationSummary(htmlHelper, excludePropertyErrors, message, (object)null /* htmlAttributes */);
+ }
+
+ public static MvcHtmlString ValidationSummary(this HtmlHelper htmlHelper, string message, object htmlAttributes)
+ {
+ return ValidationSummary(htmlHelper, false /* excludePropertyErrors */, message, HtmlHelper.AnonymousObjectToHtmlAttributes(htmlAttributes));
+ }
+
+ public static MvcHtmlString ValidationSummary(this HtmlHelper htmlHelper, bool excludePropertyErrors, string message, object htmlAttributes)
+ {
+ return ValidationSummary(htmlHelper, excludePropertyErrors, message, HtmlHelper.AnonymousObjectToHtmlAttributes(htmlAttributes));
+ }
+
+ public static MvcHtmlString ValidationSummary(this HtmlHelper htmlHelper, string message, IDictionary<string, object> htmlAttributes)
+ {
+ return ValidationSummary(htmlHelper, false /* excludePropertyErrors */, message, htmlAttributes);
+ }
+
+ public static MvcHtmlString ValidationSummary(this HtmlHelper htmlHelper, bool excludePropertyErrors, string message, IDictionary<string, object> htmlAttributes)
+ {
+ if (htmlHelper == null)
+ {
+ throw new ArgumentNullException("htmlHelper");
+ }
+
+ FormContext formContext = htmlHelper.ViewContext.GetFormContextForClientValidation();
+ if (htmlHelper.ViewData.ModelState.IsValid)
+ {
+ if (formContext == null)
+ {
+ // No client side validation
+ return null;
+ }
+ // TODO: This isn't really about unobtrusive; can we fix up non-unobtrusive to get rid of this, too?
+ if (htmlHelper.ViewContext.UnobtrusiveJavaScriptEnabled && excludePropertyErrors)
+ {
+ // No client-side updates
+ return null;
+ }
+ }
+
+ string messageSpan;
+ if (!String.IsNullOrEmpty(message))
+ {
+ TagBuilder spanTag = new TagBuilder("span");
+ spanTag.SetInnerText(message);
+ messageSpan = spanTag.ToString(TagRenderMode.Normal) + Environment.NewLine;
+ }
+ else
+ {
+ messageSpan = null;
+ }
+
+ StringBuilder htmlSummary = new StringBuilder();
+ TagBuilder unorderedList = new TagBuilder("ul");
+
+ IEnumerable<ModelState> modelStates = GetModelStateList(htmlHelper, excludePropertyErrors);
+
+ foreach (ModelState modelState in modelStates)
+ {
+ foreach (ModelError modelError in modelState.Errors)
+ {
+ string errorText = GetUserErrorMessageOrDefault(htmlHelper.ViewContext.HttpContext, modelError, null /* modelState */);
+ if (!String.IsNullOrEmpty(errorText))
+ {
+ TagBuilder listItem = new TagBuilder("li");
+ listItem.SetInnerText(errorText);
+ htmlSummary.AppendLine(listItem.ToString(TagRenderMode.Normal));
+ }
+ }
+ }
+
+ if (htmlSummary.Length == 0)
+ {
+ htmlSummary.AppendLine(HiddenListItem);
+ }
+
+ unorderedList.InnerHtml = htmlSummary.ToString();
+
+ TagBuilder divBuilder = new TagBuilder("div");
+ divBuilder.MergeAttributes(htmlAttributes);
+ divBuilder.AddCssClass((htmlHelper.ViewData.ModelState.IsValid) ? HtmlHelper.ValidationSummaryValidCssClassName : HtmlHelper.ValidationSummaryCssClassName);
+ divBuilder.InnerHtml = messageSpan + unorderedList.ToString(TagRenderMode.Normal);
+
+ if (formContext != null)
+ {
+ if (htmlHelper.ViewContext.UnobtrusiveJavaScriptEnabled)
+ {
+ if (!excludePropertyErrors)
+ {
+ // Only put errors in the validation summary if they're supposed to be included there
+ divBuilder.MergeAttribute("data-valmsg-summary", "true");
+ }
+ }
+ else
+ {
+ // client val summaries need an ID
+ divBuilder.GenerateId("validationSummary");
+ formContext.ValidationSummaryId = divBuilder.Attributes["id"];
+ formContext.ReplaceValidationSummary = !excludePropertyErrors;
+ }
+ }
+ return divBuilder.ToMvcHtmlString(TagRenderMode.Normal);
+ }
+
+ // Returns non-null list of model states, which caller will render in order provided.
+ private static IEnumerable<ModelState> GetModelStateList(HtmlHelper htmlHelper, bool excludePropertyErrors)
+ {
+ if (excludePropertyErrors)
+ {
+ ModelState ms;
+ htmlHelper.ViewData.ModelState.TryGetValue(htmlHelper.ViewData.TemplateInfo.HtmlFieldPrefix, out ms);
+ if (ms != null)
+ {
+ return new ModelState[] { ms };
+ }
+
+ return new ModelState[0];
+ }
+ else
+ {
+ // Sort modelStates to respect the ordering in the metadata.
+ // ModelState doesn't refer to ModelMetadata, but we can correlate via the property name.
+ Dictionary<string, int> ordering = new Dictionary<string, int>();
+
+ var metadata = htmlHelper.ViewData.ModelMetadata;
+ if (metadata != null)
+ {
+ foreach (ModelMetadata m in metadata.Properties)
+ {
+ ordering[m.PropertyName] = m.Order;
+ }
+ }
+
+ return from kv in htmlHelper.ViewData.ModelState
+ let name = kv.Key
+ orderby ordering.GetOrDefault(name, ModelMetadata.DefaultOrder)
+ select kv.Value;
+ }
+ }
+ }
+}
diff --git a/src/System.Web.Mvc/Html/ValueExtensions.cs b/src/System.Web.Mvc/Html/ValueExtensions.cs
new file mode 100644
index 00000000..53e297c7
--- /dev/null
+++ b/src/System.Web.Mvc/Html/ValueExtensions.cs
@@ -0,0 +1,80 @@
+using System.Diagnostics.CodeAnalysis;
+using System.Linq.Expressions;
+
+namespace System.Web.Mvc.Html
+{
+ public static class ValueExtensions
+ {
+ public static MvcHtmlString Value(this HtmlHelper html, string name)
+ {
+ return Value(html, name, format: null);
+ }
+
+ public static MvcHtmlString Value(this HtmlHelper html, string name, string format)
+ {
+ if (name == null)
+ {
+ throw new ArgumentNullException("name");
+ }
+
+ return ValueForHelper(html, name, value: null, format: format, useViewData: true);
+ }
+
+ [SuppressMessage("Microsoft.Design", "CA1006:DoNotNestGenericTypesInMemberSignatures", Justification = "This is an appropriate nesting of generic types")]
+ public static MvcHtmlString ValueFor<TModel, TProperty>(this HtmlHelper<TModel> html, Expression<Func<TModel, TProperty>> expression)
+ {
+ return ValueFor(html, expression, format: null);
+ }
+
+ [SuppressMessage("Microsoft.Design", "CA1006:DoNotNestGenericTypesInMemberSignatures", Justification = "This is an appropriate nesting of generic types")]
+ public static MvcHtmlString ValueFor<TModel, TProperty>(this HtmlHelper<TModel> html, Expression<Func<TModel, TProperty>> expression, string format)
+ {
+ ModelMetadata metadata = ModelMetadata.FromLambdaExpression(expression, html.ViewData);
+ return ValueForHelper(html, ExpressionHelper.GetExpressionText(expression), metadata.Model, format, useViewData: false);
+ }
+
+ public static MvcHtmlString ValueForModel(this HtmlHelper html)
+ {
+ return ValueForModel(html, format: null);
+ }
+
+ public static MvcHtmlString ValueForModel(this HtmlHelper html, string format)
+ {
+ return Value(html, String.Empty, format);
+ }
+
+ internal static MvcHtmlString ValueForHelper(HtmlHelper html, string name, object value, string format, bool useViewData)
+ {
+ string fullName = html.ViewContext.ViewData.TemplateInfo.GetFullHtmlFieldName(name);
+ string attemptedValue = (string)html.GetModelStateValue(fullName, typeof(string));
+ string resolvedValue;
+
+ if (attemptedValue != null)
+ {
+ // case 1: if ModelState has a value then it's already formatted so ignore format string
+ resolvedValue = attemptedValue;
+ }
+ else if (useViewData)
+ {
+ if (name.Length == 0)
+ {
+ // case 2(a): format the value from ModelMetadata for the current model
+ ModelMetadata metadata = ModelMetadata.FromStringExpression(String.Empty, html.ViewContext.ViewData);
+ resolvedValue = html.FormatValue(metadata.Model, format);
+ }
+ else
+ {
+ // case 2(b): format the value from ViewData
+ resolvedValue = html.EvalString(name, format);
+ }
+ }
+ else
+ {
+ // case 3: format the explicit value from ModelMetadata
+ resolvedValue = html.FormatValue(value, format);
+ }
+
+ return MvcHtmlString.Create(html.AttributeEncode(resolvedValue));
+ }
+ }
+}
diff --git a/src/System.Web.Mvc/HtmlHelper.cs b/src/System.Web.Mvc/HtmlHelper.cs
new file mode 100644
index 00000000..c6ab7021
--- /dev/null
+++ b/src/System.Web.Mvc/HtmlHelper.cs
@@ -0,0 +1,451 @@
+using System.Collections.Generic;
+using System.ComponentModel;
+using System.Diagnostics.CodeAnalysis;
+using System.Globalization;
+using System.IO;
+using System.Linq;
+using System.Text;
+using System.Web.Helpers;
+using System.Web.Mvc.Properties;
+using System.Web.Routing;
+
+namespace System.Web.Mvc
+{
+ public class HtmlHelper
+ {
+ public static readonly string ValidationInputCssClassName = "input-validation-error";
+ public static readonly string ValidationInputValidCssClassName = "input-validation-valid";
+ public static readonly string ValidationMessageCssClassName = "field-validation-error";
+ public static readonly string ValidationMessageValidCssClassName = "field-validation-valid";
+ public static readonly string ValidationSummaryCssClassName = "validation-summary-errors";
+ public static readonly string ValidationSummaryValidCssClassName = "validation-summary-valid";
+
+ private DynamicViewDataDictionary _dynamicViewDataDictionary;
+
+ public HtmlHelper(ViewContext viewContext, IViewDataContainer viewDataContainer)
+ : this(viewContext, viewDataContainer, RouteTable.Routes)
+ {
+ }
+
+ public HtmlHelper(ViewContext viewContext, IViewDataContainer viewDataContainer, RouteCollection routeCollection)
+ {
+ if (viewContext == null)
+ {
+ throw new ArgumentNullException("viewContext");
+ }
+ if (viewDataContainer == null)
+ {
+ throw new ArgumentNullException("viewDataContainer");
+ }
+ if (routeCollection == null)
+ {
+ throw new ArgumentNullException("routeCollection");
+ }
+
+ ViewContext = viewContext;
+ ViewDataContainer = viewDataContainer;
+ RouteCollection = routeCollection;
+ ClientValidationRuleFactory = (name, metadata) => ModelValidatorProviders.Providers.GetValidators(metadata ?? ModelMetadata.FromStringExpression(name, ViewData), ViewContext).SelectMany(v => v.GetClientValidationRules());
+ }
+
+ public static bool ClientValidationEnabled
+ {
+ get { return ViewContext.GetClientValidationEnabled(); }
+ set { ViewContext.SetClientValidationEnabled(value); }
+ }
+
+ public static string IdAttributeDotReplacement
+ {
+ get { return WebPages.Html.HtmlHelper.IdAttributeDotReplacement; }
+ set { WebPages.Html.HtmlHelper.IdAttributeDotReplacement = value; }
+ }
+
+ internal Func<string, ModelMetadata, IEnumerable<ModelClientValidationRule>> ClientValidationRuleFactory { get; set; }
+
+ public RouteCollection RouteCollection { get; private set; }
+
+ public static bool UnobtrusiveJavaScriptEnabled
+ {
+ get { return ViewContext.GetUnobtrusiveJavaScriptEnabled(); }
+ set { ViewContext.SetUnobtrusiveJavaScriptEnabled(value); }
+ }
+
+ public dynamic ViewBag
+ {
+ get
+ {
+ if (_dynamicViewDataDictionary == null)
+ {
+ _dynamicViewDataDictionary = new DynamicViewDataDictionary(() => ViewData);
+ }
+ return _dynamicViewDataDictionary;
+ }
+ }
+
+ public ViewContext ViewContext { get; private set; }
+
+ public ViewDataDictionary ViewData
+ {
+ get { return ViewDataContainer.ViewData; }
+ }
+
+ public IViewDataContainer ViewDataContainer { get; internal set; }
+
+ public static RouteValueDictionary AnonymousObjectToHtmlAttributes(object htmlAttributes)
+ {
+ RouteValueDictionary result = new RouteValueDictionary();
+
+ if (htmlAttributes != null)
+ {
+ foreach (PropertyDescriptor property in TypeDescriptor.GetProperties(htmlAttributes))
+ {
+ result.Add(property.Name.Replace('_', '-'), property.GetValue(htmlAttributes));
+ }
+ }
+
+ return result;
+ }
+
+ public MvcHtmlString AntiForgeryToken()
+ {
+ return AntiForgeryToken(salt: null);
+ }
+
+ public MvcHtmlString AntiForgeryToken(string salt)
+ {
+ return AntiForgeryToken(salt, domain: null, path: null);
+ }
+
+ public MvcHtmlString AntiForgeryToken(string salt, string domain, string path)
+ {
+ return new MvcHtmlString(AntiForgery.GetHtml(ViewContext.HttpContext, salt, domain, path).ToString());
+ }
+
+ [SuppressMessage("Microsoft.Performance", "CA1822:MarkMembersAsStatic", Justification = "For consistency, all helpers are instance methods.")]
+ public string AttributeEncode(string value)
+ {
+ return (!String.IsNullOrEmpty(value)) ? HttpUtility.HtmlAttributeEncode(value) : String.Empty;
+ }
+
+ public string AttributeEncode(object value)
+ {
+ return AttributeEncode(Convert.ToString(value, CultureInfo.InvariantCulture));
+ }
+
+ public void EnableClientValidation()
+ {
+ EnableClientValidation(enabled: true);
+ }
+
+ public void EnableClientValidation(bool enabled)
+ {
+ ViewContext.ClientValidationEnabled = enabled;
+ }
+
+ public void EnableUnobtrusiveJavaScript()
+ {
+ EnableUnobtrusiveJavaScript(enabled: true);
+ }
+
+ public void EnableUnobtrusiveJavaScript(bool enabled)
+ {
+ ViewContext.UnobtrusiveJavaScriptEnabled = enabled;
+ }
+
+ [SuppressMessage("Microsoft.Performance", "CA1822:MarkMembersAsStatic", Justification = "For consistency, all helpers are instance methods.")]
+ public string Encode(string value)
+ {
+ return (!String.IsNullOrEmpty(value)) ? HttpUtility.HtmlEncode(value) : String.Empty;
+ }
+
+ [SuppressMessage("Microsoft.Performance", "CA1822:MarkMembersAsStatic", Justification = "For consistency, all helpers are instance methods.")]
+ public string Encode(object value)
+ {
+ return value != null ? HttpUtility.HtmlEncode(value) : String.Empty;
+ }
+
+ internal string EvalString(string key)
+ {
+ return Convert.ToString(ViewData.Eval(key), CultureInfo.CurrentCulture);
+ }
+
+ internal string EvalString(string key, string format)
+ {
+ return Convert.ToString(ViewData.Eval(key, format), CultureInfo.CurrentCulture);
+ }
+
+ [SuppressMessage("Microsoft.Performance", "CA1822:MarkMembersAsStatic", Justification = "For consistency, all helpers are instance methods.")]
+ public string FormatValue(object value, string format)
+ {
+ return ViewDataDictionary.FormatValueInternal(value, format);
+ }
+
+ internal bool EvalBoolean(string key)
+ {
+ return Convert.ToBoolean(ViewData.Eval(key), CultureInfo.InvariantCulture);
+ }
+
+ internal static IView FindPartialView(ViewContext viewContext, string partialViewName, ViewEngineCollection viewEngineCollection)
+ {
+ ViewEngineResult result = viewEngineCollection.FindPartialView(viewContext, partialViewName);
+ if (result.View != null)
+ {
+ return result.View;
+ }
+
+ StringBuilder locationsText = new StringBuilder();
+ foreach (string location in result.SearchedLocations)
+ {
+ locationsText.AppendLine();
+ locationsText.Append(location);
+ }
+
+ throw new InvalidOperationException(String.Format(CultureInfo.CurrentCulture,
+ MvcResources.Common_PartialViewNotFound, partialViewName, locationsText));
+ }
+
+ public static string GenerateIdFromName(string name)
+ {
+ return GenerateIdFromName(name, IdAttributeDotReplacement);
+ }
+
+ public static string GenerateIdFromName(string name, string idAttributeDotReplacement)
+ {
+ if (name == null)
+ {
+ throw new ArgumentNullException("name");
+ }
+
+ if (idAttributeDotReplacement == null)
+ {
+ throw new ArgumentNullException("idAttributeDotReplacement");
+ }
+
+ // TagBuilder.CreateSanitizedId returns null for empty strings, return String.Empty instead to avoid breaking change
+ if (name.Length == 0)
+ {
+ return String.Empty;
+ }
+
+ return TagBuilder.CreateSanitizedId(name, idAttributeDotReplacement);
+ }
+
+ public static string GenerateLink(RequestContext requestContext, RouteCollection routeCollection, string linkText, string routeName, string actionName, string controllerName, RouteValueDictionary routeValues, IDictionary<string, object> htmlAttributes)
+ {
+ return GenerateLink(requestContext, routeCollection, linkText, routeName, actionName, controllerName, null /* protocol */, null /* hostName */, null /* fragment */, routeValues, htmlAttributes);
+ }
+
+ public static string GenerateLink(RequestContext requestContext, RouteCollection routeCollection, string linkText, string routeName, string actionName, string controllerName, string protocol, string hostName, string fragment, RouteValueDictionary routeValues, IDictionary<string, object> htmlAttributes)
+ {
+ return GenerateLinkInternal(requestContext, routeCollection, linkText, routeName, actionName, controllerName, protocol, hostName, fragment, routeValues, htmlAttributes, true /* includeImplicitMvcValues */);
+ }
+
+ private static string GenerateLinkInternal(RequestContext requestContext, RouteCollection routeCollection, string linkText, string routeName, string actionName, string controllerName, string protocol, string hostName, string fragment, RouteValueDictionary routeValues, IDictionary<string, object> htmlAttributes, bool includeImplicitMvcValues)
+ {
+ string url = UrlHelper.GenerateUrl(routeName, actionName, controllerName, protocol, hostName, fragment, routeValues, routeCollection, requestContext, includeImplicitMvcValues);
+ TagBuilder tagBuilder = new TagBuilder("a")
+ {
+ InnerHtml = (!String.IsNullOrEmpty(linkText)) ? HttpUtility.HtmlEncode(linkText) : String.Empty
+ };
+ tagBuilder.MergeAttributes(htmlAttributes);
+ tagBuilder.MergeAttribute("href", url);
+ return tagBuilder.ToString(TagRenderMode.Normal);
+ }
+
+ public static string GenerateRouteLink(RequestContext requestContext, RouteCollection routeCollection, string linkText, string routeName, RouteValueDictionary routeValues, IDictionary<string, object> htmlAttributes)
+ {
+ return GenerateRouteLink(requestContext, routeCollection, linkText, routeName, null /* protocol */, null /* hostName */, null /* fragment */, routeValues, htmlAttributes);
+ }
+
+ public static string GenerateRouteLink(RequestContext requestContext, RouteCollection routeCollection, string linkText, string routeName, string protocol, string hostName, string fragment, RouteValueDictionary routeValues, IDictionary<string, object> htmlAttributes)
+ {
+ return GenerateLinkInternal(requestContext, routeCollection, linkText, routeName, null /* actionName */, null /* controllerName */, protocol, hostName, fragment, routeValues, htmlAttributes, false /* includeImplicitMvcValues */);
+ }
+
+ public static string GetFormMethodString(FormMethod method)
+ {
+ switch (method)
+ {
+ case FormMethod.Get:
+ return "get";
+ case FormMethod.Post:
+ return "post";
+ default:
+ return "post";
+ }
+ }
+
+ public static string GetInputTypeString(InputType inputType)
+ {
+ switch (inputType)
+ {
+ case InputType.CheckBox:
+ return "checkbox";
+ case InputType.Hidden:
+ return "hidden";
+ case InputType.Password:
+ return "password";
+ case InputType.Radio:
+ return "radio";
+ case InputType.Text:
+ return "text";
+ default:
+ return "text";
+ }
+ }
+
+ internal object GetModelStateValue(string key, Type destinationType)
+ {
+ ModelState modelState;
+ if (ViewData.ModelState.TryGetValue(key, out modelState))
+ {
+ if (modelState.Value != null)
+ {
+ return modelState.Value.ConvertTo(destinationType, null /* culture */);
+ }
+ }
+ return null;
+ }
+
+ public IDictionary<string, object> GetUnobtrusiveValidationAttributes(string name)
+ {
+ return GetUnobtrusiveValidationAttributes(name, metadata: null);
+ }
+
+ // Only render attributes if unobtrusive client-side validation is enabled, and then only if we've
+ // never rendered validation for a field with this name in this form. Also, if there's no form context,
+ // then we can't render the attributes (we'd have no <form> to attach them to).
+ public IDictionary<string, object> GetUnobtrusiveValidationAttributes(string name, ModelMetadata metadata)
+ {
+ Dictionary<string, object> results = new Dictionary<string, object>();
+
+ // The ordering of these 3 checks (and the early exits) is for performance reasons.
+ if (!ViewContext.UnobtrusiveJavaScriptEnabled)
+ {
+ return results;
+ }
+
+ FormContext formContext = ViewContext.GetFormContextForClientValidation();
+ if (formContext == null)
+ {
+ return results;
+ }
+
+ string fullName = ViewData.TemplateInfo.GetFullHtmlFieldName(name);
+ if (formContext.RenderedField(fullName))
+ {
+ return results;
+ }
+
+ formContext.RenderedField(fullName, true);
+
+ IEnumerable<ModelClientValidationRule> clientRules = ClientValidationRuleFactory(name, metadata);
+ UnobtrusiveValidationAttributesGenerator.GetValidationAttributes(clientRules, results);
+
+ return results;
+ }
+
+ public MvcHtmlString HttpMethodOverride(HttpVerbs httpVerb)
+ {
+ string httpMethod;
+ switch (httpVerb)
+ {
+ case HttpVerbs.Delete:
+ httpMethod = "DELETE";
+ break;
+ case HttpVerbs.Head:
+ httpMethod = "HEAD";
+ break;
+ case HttpVerbs.Put:
+ httpMethod = "PUT";
+ break;
+ default:
+ throw new ArgumentException(MvcResources.HtmlHelper_InvalidHttpVerb, "httpVerb");
+ }
+
+ return HttpMethodOverride(httpMethod);
+ }
+
+ [SuppressMessage("Microsoft.Performance", "CA1822:MarkMembersAsStatic", Justification = "For consistency, all helpers are instance methods.")]
+ public MvcHtmlString HttpMethodOverride(string httpMethod)
+ {
+ if (String.IsNullOrEmpty(httpMethod))
+ {
+ throw new ArgumentException(MvcResources.Common_NullOrEmpty, "httpMethod");
+ }
+ if (String.Equals(httpMethod, "GET", StringComparison.OrdinalIgnoreCase) ||
+ String.Equals(httpMethod, "POST", StringComparison.OrdinalIgnoreCase))
+ {
+ throw new ArgumentException(MvcResources.HtmlHelper_InvalidHttpMethod, "httpMethod");
+ }
+
+ TagBuilder tagBuilder = new TagBuilder("input");
+ tagBuilder.Attributes["type"] = "hidden";
+ tagBuilder.Attributes["name"] = HttpRequestExtensions.XHttpMethodOverrideKey;
+ tagBuilder.Attributes["value"] = httpMethod;
+
+ return tagBuilder.ToMvcHtmlString(TagRenderMode.SelfClosing);
+ }
+
+ /// <summary>
+ /// Wraps HTML markup in an IHtmlString, which will enable HTML markup to be
+ /// rendered to the output without getting HTML encoded.
+ /// </summary>
+ /// <param name="value">HTML markup string.</param>
+ /// <returns>An IHtmlString that represents HTML markup.</returns>
+ [SuppressMessage("Microsoft.Performance", "CA1822:MarkMembersAsStatic", Justification = "For consistency, all helpers are instance methods.")]
+ public IHtmlString Raw(string value)
+ {
+ return new HtmlString(value);
+ }
+
+ /// <summary>
+ /// Wraps HTML markup from the string representation of an object in an IHtmlString,
+ /// which will enable HTML markup to be rendered to the output without getting HTML encoded.
+ /// </summary>
+ /// <param name="value">object with string representation as HTML markup</param>
+ /// <returns>An IHtmlString that represents HTML markup.</returns>
+ [SuppressMessage("Microsoft.Performance", "CA1822:MarkMembersAsStatic", Justification = "For consistency, all helpers are instance methods.")]
+ public IHtmlString Raw(object value)
+ {
+ return new HtmlString(value == null ? null : value.ToString());
+ }
+
+ internal virtual void RenderPartialInternal(string partialViewName, ViewDataDictionary viewData, object model, TextWriter writer, ViewEngineCollection viewEngineCollection)
+ {
+ if (String.IsNullOrEmpty(partialViewName))
+ {
+ throw new ArgumentException(MvcResources.Common_NullOrEmpty, "partialViewName");
+ }
+
+ ViewDataDictionary newViewData = null;
+
+ if (model == null)
+ {
+ if (viewData == null)
+ {
+ newViewData = new ViewDataDictionary(ViewData);
+ }
+ else
+ {
+ newViewData = new ViewDataDictionary(viewData);
+ }
+ }
+ else
+ {
+ if (viewData == null)
+ {
+ newViewData = new ViewDataDictionary(model);
+ }
+ else
+ {
+ newViewData = new ViewDataDictionary(viewData) { Model = model };
+ }
+ }
+
+ ViewContext newViewContext = new ViewContext(ViewContext, ViewContext.View, newViewData, ViewContext.TempData, writer);
+ IView view = FindPartialView(newViewContext, partialViewName, viewEngineCollection);
+ view.Render(newViewContext, writer);
+ }
+ }
+}
diff --git a/src/System.Web.Mvc/HtmlHelper`1.cs b/src/System.Web.Mvc/HtmlHelper`1.cs
new file mode 100644
index 00000000..77eb44a7
--- /dev/null
+++ b/src/System.Web.Mvc/HtmlHelper`1.cs
@@ -0,0 +1,39 @@
+using System.Web.Routing;
+
+namespace System.Web.Mvc
+{
+ public class HtmlHelper<TModel> : HtmlHelper
+ {
+ private DynamicViewDataDictionary _dynamicViewDataDictionary;
+ private ViewDataDictionary<TModel> _viewData;
+
+ public HtmlHelper(ViewContext viewContext, IViewDataContainer viewDataContainer)
+ : this(viewContext, viewDataContainer, RouteTable.Routes)
+ {
+ }
+
+ public HtmlHelper(ViewContext viewContext, IViewDataContainer viewDataContainer, RouteCollection routeCollection)
+ : base(viewContext, viewDataContainer, routeCollection)
+ {
+ _viewData = new ViewDataDictionary<TModel>(viewDataContainer.ViewData);
+ }
+
+ public new dynamic ViewBag
+ {
+ get
+ {
+ if (_dynamicViewDataDictionary == null)
+ {
+ _dynamicViewDataDictionary = new DynamicViewDataDictionary(() => ViewData);
+ }
+
+ return _dynamicViewDataDictionary;
+ }
+ }
+
+ public new ViewDataDictionary<TModel> ViewData
+ {
+ get { return _viewData; }
+ }
+ }
+}
diff --git a/src/System.Web.Mvc/HttpDeleteAttribute.cs b/src/System.Web.Mvc/HttpDeleteAttribute.cs
new file mode 100644
index 00000000..5e661dfa
--- /dev/null
+++ b/src/System.Web.Mvc/HttpDeleteAttribute.cs
@@ -0,0 +1,15 @@
+using System.Reflection;
+
+namespace System.Web.Mvc
+{
+ [AttributeUsage(AttributeTargets.Method, AllowMultiple = false, Inherited = true)]
+ public sealed class HttpDeleteAttribute : ActionMethodSelectorAttribute
+ {
+ private static readonly AcceptVerbsAttribute _innerAttribute = new AcceptVerbsAttribute(HttpVerbs.Delete);
+
+ public override bool IsValidForRequest(ControllerContext controllerContext, MethodInfo methodInfo)
+ {
+ return _innerAttribute.IsValidForRequest(controllerContext, methodInfo);
+ }
+ }
+}
diff --git a/src/System.Web.Mvc/HttpFileCollectionValueProvider.cs b/src/System.Web.Mvc/HttpFileCollectionValueProvider.cs
new file mode 100644
index 00000000..3c3760eb
--- /dev/null
+++ b/src/System.Web.Mvc/HttpFileCollectionValueProvider.cs
@@ -0,0 +1,44 @@
+using System.Collections.Generic;
+using System.Globalization;
+using System.Linq;
+
+namespace System.Web.Mvc
+{
+ public sealed class HttpFileCollectionValueProvider : DictionaryValueProvider<HttpPostedFileBase[]>
+ {
+ private static readonly Dictionary<string, HttpPostedFileBase[]> _emptyDictionary = new Dictionary<string, HttpPostedFileBase[]>();
+
+ public HttpFileCollectionValueProvider(ControllerContext controllerContext)
+ : base(GetHttpPostedFileDictionary(controllerContext), CultureInfo.InvariantCulture)
+ {
+ }
+
+ private static Dictionary<string, HttpPostedFileBase[]> GetHttpPostedFileDictionary(ControllerContext controllerContext)
+ {
+ HttpFileCollectionBase files = controllerContext.HttpContext.Request.Files;
+
+ // fast-track common case of no files
+ if (files.Count == 0)
+ {
+ return _emptyDictionary;
+ }
+
+ // build up the 1:many file mapping
+ List<KeyValuePair<string, HttpPostedFileBase>> mapping = new List<KeyValuePair<string, HttpPostedFileBase>>();
+ string[] allKeys = files.AllKeys;
+ for (int i = 0; i < files.Count; i++)
+ {
+ string key = allKeys[i];
+ if (key != null)
+ {
+ HttpPostedFileBase file = HttpPostedFileBaseModelBinder.ChooseFileOrNull(files[i]);
+ mapping.Add(new KeyValuePair<string, HttpPostedFileBase>(key, file));
+ }
+ }
+
+ // turn the mapping into a 1:many dictionary
+ var grouped = mapping.GroupBy(el => el.Key, el => el.Value, StringComparer.OrdinalIgnoreCase);
+ return grouped.ToDictionary(g => g.Key, g => g.ToArray(), StringComparer.OrdinalIgnoreCase);
+ }
+ }
+}
diff --git a/src/System.Web.Mvc/HttpFileCollectionValueProviderFactory.cs b/src/System.Web.Mvc/HttpFileCollectionValueProviderFactory.cs
new file mode 100644
index 00000000..0c93d4dd
--- /dev/null
+++ b/src/System.Web.Mvc/HttpFileCollectionValueProviderFactory.cs
@@ -0,0 +1,15 @@
+namespace System.Web.Mvc
+{
+ public sealed class HttpFileCollectionValueProviderFactory : ValueProviderFactory
+ {
+ public override IValueProvider GetValueProvider(ControllerContext controllerContext)
+ {
+ if (controllerContext == null)
+ {
+ throw new ArgumentNullException("controllerContext");
+ }
+
+ return new HttpFileCollectionValueProvider(controllerContext);
+ }
+ }
+}
diff --git a/src/System.Web.Mvc/HttpGetAttribute.cs b/src/System.Web.Mvc/HttpGetAttribute.cs
new file mode 100644
index 00000000..41029ceb
--- /dev/null
+++ b/src/System.Web.Mvc/HttpGetAttribute.cs
@@ -0,0 +1,15 @@
+using System.Reflection;
+
+namespace System.Web.Mvc
+{
+ [AttributeUsage(AttributeTargets.Method, AllowMultiple = false, Inherited = true)]
+ public sealed class HttpGetAttribute : ActionMethodSelectorAttribute
+ {
+ private static readonly AcceptVerbsAttribute _innerAttribute = new AcceptVerbsAttribute(HttpVerbs.Get);
+
+ public override bool IsValidForRequest(ControllerContext controllerContext, MethodInfo methodInfo)
+ {
+ return _innerAttribute.IsValidForRequest(controllerContext, methodInfo);
+ }
+ }
+}
diff --git a/src/System.Web.Mvc/HttpHandlerUtil.cs b/src/System.Web.Mvc/HttpHandlerUtil.cs
new file mode 100644
index 00000000..89d1981b
--- /dev/null
+++ b/src/System.Web.Mvc/HttpHandlerUtil.cs
@@ -0,0 +1,91 @@
+using System.Diagnostics.CodeAnalysis;
+using System.Web.Mvc.Properties;
+using System.Web.UI;
+
+namespace System.Web.Mvc
+{
+ internal static class HttpHandlerUtil
+ {
+ [SuppressMessage("Microsoft.Reliability", "CA2000:Dispose objects before losing scope", Justification = "The Dispose on Page doesn't do anything by default, and we control both of these internal types.")]
+ public static IHttpHandler WrapForServerExecute(IHttpHandler httpHandler)
+ {
+ // Since Server.Execute() doesn't propagate HttpExceptions where the status code is
+ // anything other than 500, we need to wrap these exceptions ourselves.
+ IHttpAsyncHandler asyncHandler = httpHandler as IHttpAsyncHandler;
+ return (asyncHandler != null) ? new ServerExecuteHttpHandlerAsyncWrapper(asyncHandler) : new ServerExecuteHttpHandlerWrapper(httpHandler);
+ }
+
+ private sealed class ServerExecuteHttpHandlerAsyncWrapper : ServerExecuteHttpHandlerWrapper, IHttpAsyncHandler
+ {
+ private readonly IHttpAsyncHandler _httpHandler;
+
+ public ServerExecuteHttpHandlerAsyncWrapper(IHttpAsyncHandler httpHandler)
+ : base(httpHandler)
+ {
+ _httpHandler = httpHandler;
+ }
+
+ public IAsyncResult BeginProcessRequest(HttpContext context, AsyncCallback cb, object extraData)
+ {
+ return Wrap(() => _httpHandler.BeginProcessRequest(context, cb, extraData));
+ }
+
+ public void EndProcessRequest(IAsyncResult result)
+ {
+ Wrap(() => _httpHandler.EndProcessRequest(result));
+ }
+ }
+
+ /// <remarks>
+ /// Server.Execute() requires that the provided IHttpHandler subclass Page.
+ /// </remarks>
+ internal class ServerExecuteHttpHandlerWrapper : Page
+ {
+ private readonly IHttpHandler _httpHandler;
+
+ public ServerExecuteHttpHandlerWrapper(IHttpHandler httpHandler)
+ {
+ _httpHandler = httpHandler;
+ }
+
+ internal IHttpHandler InnerHandler
+ {
+ get { return _httpHandler; }
+ }
+
+ public override void ProcessRequest(HttpContext context)
+ {
+ Wrap(() => _httpHandler.ProcessRequest(context));
+ }
+
+ protected static void Wrap(Action action)
+ {
+ Wrap(delegate
+ {
+ action();
+ return (object)null;
+ });
+ }
+
+ protected static TResult Wrap<TResult>(Func<TResult> func)
+ {
+ try
+ {
+ return func();
+ }
+ catch (HttpException he)
+ {
+ if (he.GetHttpCode() == 500)
+ {
+ throw; // doesn't need to be wrapped
+ }
+ else
+ {
+ HttpException newHe = new HttpException(500, MvcResources.ViewPageHttpHandlerWrapper_ExceptionOccurred, he);
+ throw newHe;
+ }
+ }
+ }
+ }
+ }
+}
diff --git a/src/System.Web.Mvc/HttpNotFoundResult.cs b/src/System.Web.Mvc/HttpNotFoundResult.cs
new file mode 100644
index 00000000..9b749ace
--- /dev/null
+++ b/src/System.Web.Mvc/HttpNotFoundResult.cs
@@ -0,0 +1,18 @@
+using System.Net;
+
+namespace System.Web.Mvc
+{
+ public class HttpNotFoundResult : HttpStatusCodeResult
+ {
+ public HttpNotFoundResult()
+ : this(null)
+ {
+ }
+
+ // NotFound is equivalent to HTTP status 404.
+ public HttpNotFoundResult(string statusDescription)
+ : base(HttpStatusCode.NotFound, statusDescription)
+ {
+ }
+ }
+}
diff --git a/src/System.Web.Mvc/HttpPostAttribute.cs b/src/System.Web.Mvc/HttpPostAttribute.cs
new file mode 100644
index 00000000..da4e10cc
--- /dev/null
+++ b/src/System.Web.Mvc/HttpPostAttribute.cs
@@ -0,0 +1,15 @@
+using System.Reflection;
+
+namespace System.Web.Mvc
+{
+ [AttributeUsage(AttributeTargets.Method, AllowMultiple = false, Inherited = true)]
+ public sealed class HttpPostAttribute : ActionMethodSelectorAttribute
+ {
+ private static readonly AcceptVerbsAttribute _innerAttribute = new AcceptVerbsAttribute(HttpVerbs.Post);
+
+ public override bool IsValidForRequest(ControllerContext controllerContext, MethodInfo methodInfo)
+ {
+ return _innerAttribute.IsValidForRequest(controllerContext, methodInfo);
+ }
+ }
+}
diff --git a/src/System.Web.Mvc/HttpPostedFileBaseModelBinder.cs b/src/System.Web.Mvc/HttpPostedFileBaseModelBinder.cs
new file mode 100644
index 00000000..43545acc
--- /dev/null
+++ b/src/System.Web.Mvc/HttpPostedFileBaseModelBinder.cs
@@ -0,0 +1,39 @@
+namespace System.Web.Mvc
+{
+ public class HttpPostedFileBaseModelBinder : IModelBinder
+ {
+ public object BindModel(ControllerContext controllerContext, ModelBindingContext bindingContext)
+ {
+ if (controllerContext == null)
+ {
+ throw new ArgumentNullException("controllerContext");
+ }
+ if (bindingContext == null)
+ {
+ throw new ArgumentNullException("bindingContext");
+ }
+
+ HttpPostedFileBase theFile = controllerContext.HttpContext.Request.Files[bindingContext.ModelName];
+ return ChooseFileOrNull(theFile);
+ }
+
+ // helper that returns the original file if there was content uploaded, null if empty
+ internal static HttpPostedFileBase ChooseFileOrNull(HttpPostedFileBase rawFile)
+ {
+ // case 1: there was no <input type="file" ... /> element in the post
+ if (rawFile == null)
+ {
+ return null;
+ }
+
+ // case 2: there was an <input type="file" ... /> element in the post, but it was left blank
+ if (rawFile.ContentLength == 0 && String.IsNullOrEmpty(rawFile.FileName))
+ {
+ return null;
+ }
+
+ // case 3: the file was posted
+ return rawFile;
+ }
+ }
+}
diff --git a/src/System.Web.Mvc/HttpPutAttribute.cs b/src/System.Web.Mvc/HttpPutAttribute.cs
new file mode 100644
index 00000000..7f0fe8e3
--- /dev/null
+++ b/src/System.Web.Mvc/HttpPutAttribute.cs
@@ -0,0 +1,15 @@
+using System.Reflection;
+
+namespace System.Web.Mvc
+{
+ [AttributeUsage(AttributeTargets.Method, AllowMultiple = false, Inherited = true)]
+ public sealed class HttpPutAttribute : ActionMethodSelectorAttribute
+ {
+ private static readonly AcceptVerbsAttribute _innerAttribute = new AcceptVerbsAttribute(HttpVerbs.Put);
+
+ public override bool IsValidForRequest(ControllerContext controllerContext, MethodInfo methodInfo)
+ {
+ return _innerAttribute.IsValidForRequest(controllerContext, methodInfo);
+ }
+ }
+}
diff --git a/src/System.Web.Mvc/HttpRequestExtensions.cs b/src/System.Web.Mvc/HttpRequestExtensions.cs
new file mode 100644
index 00000000..d1719da6
--- /dev/null
+++ b/src/System.Web.Mvc/HttpRequestExtensions.cs
@@ -0,0 +1,54 @@
+namespace System.Web.Mvc
+{
+ public static class HttpRequestExtensions
+ {
+ internal const string XHttpMethodOverrideKey = "X-HTTP-Method-Override";
+
+ public static string GetHttpMethodOverride(this HttpRequestBase request)
+ {
+ if (request == null)
+ {
+ throw new ArgumentNullException("request");
+ }
+
+ string incomingVerb = request.HttpMethod;
+
+ if (!String.Equals(incomingVerb, "POST", StringComparison.OrdinalIgnoreCase))
+ {
+ return incomingVerb;
+ }
+
+ string verbOverride = null;
+ string headerOverrideValue = request.Headers[XHttpMethodOverrideKey];
+ if (!String.IsNullOrEmpty(headerOverrideValue))
+ {
+ verbOverride = headerOverrideValue;
+ }
+ else
+ {
+ string formOverrideValue = request.Form[XHttpMethodOverrideKey];
+ if (!String.IsNullOrEmpty(formOverrideValue))
+ {
+ verbOverride = formOverrideValue;
+ }
+ else
+ {
+ string queryStringOverrideValue = request.QueryString[XHttpMethodOverrideKey];
+ if (!String.IsNullOrEmpty(queryStringOverrideValue))
+ {
+ verbOverride = queryStringOverrideValue;
+ }
+ }
+ }
+ if (verbOverride != null)
+ {
+ if (!String.Equals(verbOverride, "GET", StringComparison.OrdinalIgnoreCase) &&
+ !String.Equals(verbOverride, "POST", StringComparison.OrdinalIgnoreCase))
+ {
+ incomingVerb = verbOverride;
+ }
+ }
+ return incomingVerb;
+ }
+ }
+}
diff --git a/src/System.Web.Mvc/HttpStatusCodeResult.cs b/src/System.Web.Mvc/HttpStatusCodeResult.cs
new file mode 100644
index 00000000..785e1eae
--- /dev/null
+++ b/src/System.Web.Mvc/HttpStatusCodeResult.cs
@@ -0,0 +1,46 @@
+using System.Net;
+
+namespace System.Web.Mvc
+{
+ public class HttpStatusCodeResult : ActionResult
+ {
+ public HttpStatusCodeResult(int statusCode)
+ : this(statusCode, null)
+ {
+ }
+
+ public HttpStatusCodeResult(HttpStatusCode statusCode)
+ : this(statusCode, null)
+ {
+ }
+
+ public HttpStatusCodeResult(HttpStatusCode statusCode, string statusDescription)
+ : this((int)statusCode, statusDescription)
+ {
+ }
+
+ public HttpStatusCodeResult(int statusCode, string statusDescription)
+ {
+ StatusCode = statusCode;
+ StatusDescription = statusDescription;
+ }
+
+ public int StatusCode { get; private set; }
+
+ public string StatusDescription { get; private set; }
+
+ public override void ExecuteResult(ControllerContext context)
+ {
+ if (context == null)
+ {
+ throw new ArgumentNullException("context");
+ }
+
+ context.HttpContext.Response.StatusCode = StatusCode;
+ if (StatusDescription != null)
+ {
+ context.HttpContext.Response.StatusDescription = StatusDescription;
+ }
+ }
+ }
+}
diff --git a/src/System.Web.Mvc/HttpUnauthorizedResult.cs b/src/System.Web.Mvc/HttpUnauthorizedResult.cs
new file mode 100644
index 00000000..fe672422
--- /dev/null
+++ b/src/System.Web.Mvc/HttpUnauthorizedResult.cs
@@ -0,0 +1,21 @@
+using System.Net;
+
+namespace System.Web.Mvc
+{
+ public class HttpUnauthorizedResult : HttpStatusCodeResult
+ {
+ public HttpUnauthorizedResult()
+ : this(null)
+ {
+ }
+
+ // Unauthorized is equivalent to HTTP status 401, the status code for unauthorized
+ // access. Other code might intercept this and perform some special logic. For
+ // example, the FormsAuthenticationModule looks for 401 responses and instead
+ // redirects the user to the login page.
+ public HttpUnauthorizedResult(string statusDescription)
+ : base(HttpStatusCode.Unauthorized, statusDescription)
+ {
+ }
+ }
+}
diff --git a/src/System.Web.Mvc/HttpVerbs.cs b/src/System.Web.Mvc/HttpVerbs.cs
new file mode 100644
index 00000000..22049977
--- /dev/null
+++ b/src/System.Web.Mvc/HttpVerbs.cs
@@ -0,0 +1,12 @@
+namespace System.Web.Mvc
+{
+ [Flags]
+ public enum HttpVerbs
+ {
+ Get = 1 << 0,
+ Post = 1 << 1,
+ Put = 1 << 2,
+ Delete = 1 << 3,
+ Head = 1 << 4
+ }
+}
diff --git a/src/System.Web.Mvc/IActionFilter.cs b/src/System.Web.Mvc/IActionFilter.cs
new file mode 100644
index 00000000..8ebbbca5
--- /dev/null
+++ b/src/System.Web.Mvc/IActionFilter.cs
@@ -0,0 +1,8 @@
+namespace System.Web.Mvc
+{
+ public interface IActionFilter
+ {
+ void OnActionExecuting(ActionExecutingContext filterContext);
+ void OnActionExecuted(ActionExecutedContext filterContext);
+ }
+}
diff --git a/src/System.Web.Mvc/IActionInvoker.cs b/src/System.Web.Mvc/IActionInvoker.cs
new file mode 100644
index 00000000..0cec2baf
--- /dev/null
+++ b/src/System.Web.Mvc/IActionInvoker.cs
@@ -0,0 +1,7 @@
+namespace System.Web.Mvc
+{
+ public interface IActionInvoker
+ {
+ bool InvokeAction(ControllerContext controllerContext, string actionName);
+ }
+}
diff --git a/src/System.Web.Mvc/IAuthorizationFilter.cs b/src/System.Web.Mvc/IAuthorizationFilter.cs
new file mode 100644
index 00000000..e0f01b26
--- /dev/null
+++ b/src/System.Web.Mvc/IAuthorizationFilter.cs
@@ -0,0 +1,7 @@
+namespace System.Web.Mvc
+{
+ public interface IAuthorizationFilter
+ {
+ void OnAuthorization(AuthorizationContext filterContext);
+ }
+}
diff --git a/src/System.Web.Mvc/IBuildManager.cs b/src/System.Web.Mvc/IBuildManager.cs
new file mode 100644
index 00000000..5782fbd6
--- /dev/null
+++ b/src/System.Web.Mvc/IBuildManager.cs
@@ -0,0 +1,14 @@
+using System.Collections;
+using System.IO;
+
+namespace System.Web.Mvc
+{
+ internal interface IBuildManager
+ {
+ bool FileExists(string virtualPath);
+ Type GetCompiledType(string virtualPath);
+ ICollection GetReferencedAssemblies();
+ Stream ReadCachedFile(string fileName);
+ Stream CreateCachedFile(string fileName);
+ }
+}
diff --git a/src/System.Web.Mvc/IClientValidatable.cs b/src/System.Web.Mvc/IClientValidatable.cs
new file mode 100644
index 00000000..1eeee7c4
--- /dev/null
+++ b/src/System.Web.Mvc/IClientValidatable.cs
@@ -0,0 +1,19 @@
+using System.Collections.Generic;
+
+namespace System.Web.Mvc
+{
+ // The purpose of this interface is to make something as supporting client-side
+ // validation, which could be discovered at runtime by whatever validation
+ // framework you're using. Because this interface is designed to be independent
+ // of underlying implementation details, where you apply this interface will
+ // depend on your specific validation framework.
+ //
+ // For DataAnnotations, you'll apply this interface to your validation attribute
+ // (the class which derives from ValidationAttribute). When you've implemented
+ // this interface, it will alleviate the need of writing a validator and registering
+ // it with the DataAnnotationsModelValidatorProvider.
+ public interface IClientValidatable
+ {
+ IEnumerable<ModelClientValidationRule> GetClientValidationRules(ModelMetadata metadata, ControllerContext context);
+ }
+}
diff --git a/src/System.Web.Mvc/IController.cs b/src/System.Web.Mvc/IController.cs
new file mode 100644
index 00000000..751f8d29
--- /dev/null
+++ b/src/System.Web.Mvc/IController.cs
@@ -0,0 +1,9 @@
+using System.Web.Routing;
+
+namespace System.Web.Mvc
+{
+ public interface IController
+ {
+ void Execute(RequestContext requestContext);
+ }
+}
diff --git a/src/System.Web.Mvc/IControllerActivator.cs b/src/System.Web.Mvc/IControllerActivator.cs
new file mode 100644
index 00000000..38bece0e
--- /dev/null
+++ b/src/System.Web.Mvc/IControllerActivator.cs
@@ -0,0 +1,9 @@
+using System.Web.Routing;
+
+namespace System.Web.Mvc
+{
+ public interface IControllerActivator
+ {
+ IController Create(RequestContext requestContext, Type controllerType);
+ }
+}
diff --git a/src/System.Web.Mvc/IControllerFactory.cs b/src/System.Web.Mvc/IControllerFactory.cs
new file mode 100644
index 00000000..55166ba7
--- /dev/null
+++ b/src/System.Web.Mvc/IControllerFactory.cs
@@ -0,0 +1,12 @@
+using System.Web.Routing;
+using System.Web.SessionState;
+
+namespace System.Web.Mvc
+{
+ public interface IControllerFactory
+ {
+ IController CreateController(RequestContext requestContext, string controllerName);
+ SessionStateBehavior GetControllerSessionBehavior(RequestContext requestContext, string controllerName);
+ void ReleaseController(IController controller);
+ }
+}
diff --git a/src/System.Web.Mvc/IDependencyResolver.cs b/src/System.Web.Mvc/IDependencyResolver.cs
new file mode 100644
index 00000000..fe5a6cbd
--- /dev/null
+++ b/src/System.Web.Mvc/IDependencyResolver.cs
@@ -0,0 +1,10 @@
+using System.Collections.Generic;
+
+namespace System.Web.Mvc
+{
+ public interface IDependencyResolver
+ {
+ object GetService(Type serviceType);
+ IEnumerable<object> GetServices(Type serviceType);
+ }
+}
diff --git a/src/System.Web.Mvc/IEnumerableValueProvider.cs b/src/System.Web.Mvc/IEnumerableValueProvider.cs
new file mode 100644
index 00000000..e4fe1bde
--- /dev/null
+++ b/src/System.Web.Mvc/IEnumerableValueProvider.cs
@@ -0,0 +1,10 @@
+using System.Collections.Generic;
+
+namespace System.Web.Mvc
+{
+ // Represents a special IValueProvider that has the ability to be enumerable.
+ public interface IEnumerableValueProvider : IValueProvider
+ {
+ IDictionary<string, string> GetKeysFromPrefix(string prefix);
+ }
+}
diff --git a/src/System.Web.Mvc/IExceptionFilter.cs b/src/System.Web.Mvc/IExceptionFilter.cs
new file mode 100644
index 00000000..545187e6
--- /dev/null
+++ b/src/System.Web.Mvc/IExceptionFilter.cs
@@ -0,0 +1,7 @@
+namespace System.Web.Mvc
+{
+ public interface IExceptionFilter
+ {
+ void OnException(ExceptionContext filterContext);
+ }
+}
diff --git a/src/System.Web.Mvc/IFilterProvider.cs b/src/System.Web.Mvc/IFilterProvider.cs
new file mode 100644
index 00000000..edae298e
--- /dev/null
+++ b/src/System.Web.Mvc/IFilterProvider.cs
@@ -0,0 +1,9 @@
+using System.Collections.Generic;
+
+namespace System.Web.Mvc
+{
+ public interface IFilterProvider
+ {
+ IEnumerable<Filter> GetFilters(ControllerContext controllerContext, ActionDescriptor actionDescriptor);
+ }
+}
diff --git a/src/System.Web.Mvc/IMetadataAware.cs b/src/System.Web.Mvc/IMetadataAware.cs
new file mode 100644
index 00000000..c1e9df24
--- /dev/null
+++ b/src/System.Web.Mvc/IMetadataAware.cs
@@ -0,0 +1,12 @@
+namespace System.Web.Mvc
+{
+ // This interface is implemented by attributes which wish to contribute to the
+ // ModelMetadata creation process without needing to write a custom metadata
+ // provider. It is consumed by AssociatedMetadataProvider, so this behavior is
+ // automatically inherited by all classes which derive from it (notably, the
+ // DataAnnotationsModelMetadataProvider).
+ public interface IMetadataAware
+ {
+ void OnMetadataCreated(ModelMetadata metadata);
+ }
+}
diff --git a/src/System.Web.Mvc/IModelBinder.cs b/src/System.Web.Mvc/IModelBinder.cs
new file mode 100644
index 00000000..41a488d2
--- /dev/null
+++ b/src/System.Web.Mvc/IModelBinder.cs
@@ -0,0 +1,7 @@
+namespace System.Web.Mvc
+{
+ public interface IModelBinder
+ {
+ object BindModel(ControllerContext controllerContext, ModelBindingContext bindingContext);
+ }
+}
diff --git a/src/System.Web.Mvc/IModelBinderProvider.cs b/src/System.Web.Mvc/IModelBinderProvider.cs
new file mode 100644
index 00000000..eac700a1
--- /dev/null
+++ b/src/System.Web.Mvc/IModelBinderProvider.cs
@@ -0,0 +1,7 @@
+namespace System.Web.Mvc
+{
+ public interface IModelBinderProvider
+ {
+ IModelBinder GetBinder(Type modelType);
+ }
+}
diff --git a/src/System.Web.Mvc/IMvcControlBuilder.cs b/src/System.Web.Mvc/IMvcControlBuilder.cs
new file mode 100644
index 00000000..2f7e2064
--- /dev/null
+++ b/src/System.Web.Mvc/IMvcControlBuilder.cs
@@ -0,0 +1,7 @@
+namespace System.Web.Mvc
+{
+ internal interface IMvcControlBuilder
+ {
+ string Inherits { set; }
+ }
+}
diff --git a/src/System.Web.Mvc/IMvcFilter.cs b/src/System.Web.Mvc/IMvcFilter.cs
new file mode 100644
index 00000000..05aa035f
--- /dev/null
+++ b/src/System.Web.Mvc/IMvcFilter.cs
@@ -0,0 +1,8 @@
+namespace System.Web.Mvc
+{
+ public interface IMvcFilter
+ {
+ bool AllowMultiple { get; }
+ int Order { get; }
+ }
+}
diff --git a/src/System.Web.Mvc/IResolver.cs b/src/System.Web.Mvc/IResolver.cs
new file mode 100644
index 00000000..0d0a01d6
--- /dev/null
+++ b/src/System.Web.Mvc/IResolver.cs
@@ -0,0 +1,7 @@
+namespace System.Web.Mvc
+{
+ internal interface IResolver<T>
+ {
+ T Current { get; }
+ }
+}
diff --git a/src/System.Web.Mvc/IResultFilter.cs b/src/System.Web.Mvc/IResultFilter.cs
new file mode 100644
index 00000000..892489cb
--- /dev/null
+++ b/src/System.Web.Mvc/IResultFilter.cs
@@ -0,0 +1,8 @@
+namespace System.Web.Mvc
+{
+ public interface IResultFilter
+ {
+ void OnResultExecuting(ResultExecutingContext filterContext);
+ void OnResultExecuted(ResultExecutedContext filterContext);
+ }
+}
diff --git a/src/System.Web.Mvc/IRouteWithArea.cs b/src/System.Web.Mvc/IRouteWithArea.cs
new file mode 100644
index 00000000..83fb00fc
--- /dev/null
+++ b/src/System.Web.Mvc/IRouteWithArea.cs
@@ -0,0 +1,7 @@
+namespace System.Web.Mvc
+{
+ public interface IRouteWithArea
+ {
+ string Area { get; }
+ }
+}
diff --git a/src/System.Web.Mvc/ITempDataProvider.cs b/src/System.Web.Mvc/ITempDataProvider.cs
new file mode 100644
index 00000000..7f40896c
--- /dev/null
+++ b/src/System.Web.Mvc/ITempDataProvider.cs
@@ -0,0 +1,10 @@
+using System.Collections.Generic;
+
+namespace System.Web.Mvc
+{
+ public interface ITempDataProvider
+ {
+ IDictionary<string, object> LoadTempData(ControllerContext controllerContext);
+ void SaveTempData(ControllerContext controllerContext, IDictionary<string, object> values);
+ }
+}
diff --git a/src/System.Web.Mvc/IUniquelyIdentifiable.cs b/src/System.Web.Mvc/IUniquelyIdentifiable.cs
new file mode 100644
index 00000000..a310c93a
--- /dev/null
+++ b/src/System.Web.Mvc/IUniquelyIdentifiable.cs
@@ -0,0 +1,7 @@
+namespace System.Web.Mvc
+{
+ internal interface IUniquelyIdentifiable
+ {
+ string UniqueId { get; }
+ }
+}
diff --git a/src/System.Web.Mvc/IUnvalidatedRequestValues.cs b/src/System.Web.Mvc/IUnvalidatedRequestValues.cs
new file mode 100644
index 00000000..1f5c67bc
--- /dev/null
+++ b/src/System.Web.Mvc/IUnvalidatedRequestValues.cs
@@ -0,0 +1,13 @@
+using System.Collections.Specialized;
+
+namespace System.Web.Mvc
+{
+ // Used for mocking the UnvalidatedRequestValues type in System.Web.WebPages
+
+ internal interface IUnvalidatedRequestValues
+ {
+ NameValueCollection Form { get; }
+ NameValueCollection QueryString { get; }
+ string this[string key] { get; }
+ }
+}
diff --git a/src/System.Web.Mvc/IUnvalidatedValueProvider.cs b/src/System.Web.Mvc/IUnvalidatedValueProvider.cs
new file mode 100644
index 00000000..91882c3a
--- /dev/null
+++ b/src/System.Web.Mvc/IUnvalidatedValueProvider.cs
@@ -0,0 +1,8 @@
+namespace System.Web.Mvc
+{
+ // Represents a special IValueProvider that has the ability to skip request validation.
+ public interface IUnvalidatedValueProvider : IValueProvider
+ {
+ ValueProviderResult GetValue(string key, bool skipValidation);
+ }
+}
diff --git a/src/System.Web.Mvc/IValueProvider.cs b/src/System.Web.Mvc/IValueProvider.cs
new file mode 100644
index 00000000..37b83a14
--- /dev/null
+++ b/src/System.Web.Mvc/IValueProvider.cs
@@ -0,0 +1,8 @@
+namespace System.Web.Mvc
+{
+ public interface IValueProvider
+ {
+ bool ContainsPrefix(string prefix);
+ ValueProviderResult GetValue(string key);
+ }
+}
diff --git a/src/System.Web.Mvc/IView.cs b/src/System.Web.Mvc/IView.cs
new file mode 100644
index 00000000..2d48cf70
--- /dev/null
+++ b/src/System.Web.Mvc/IView.cs
@@ -0,0 +1,9 @@
+using System.IO;
+
+namespace System.Web.Mvc
+{
+ public interface IView
+ {
+ void Render(ViewContext viewContext, TextWriter writer);
+ }
+}
diff --git a/src/System.Web.Mvc/IViewDataContainer.cs b/src/System.Web.Mvc/IViewDataContainer.cs
new file mode 100644
index 00000000..34063148
--- /dev/null
+++ b/src/System.Web.Mvc/IViewDataContainer.cs
@@ -0,0 +1,10 @@
+using System.Diagnostics.CodeAnalysis;
+
+namespace System.Web.Mvc
+{
+ public interface IViewDataContainer
+ {
+ [SuppressMessage("Microsoft.Usage", "CA2227:CollectionPropertiesShouldBeReadOnly", Justification = "This is the mechanism by which the ViewPage / ViewUserControl get their ViewDataDictionary objects.")]
+ ViewDataDictionary ViewData { get; set; }
+ }
+}
diff --git a/src/System.Web.Mvc/IViewEngine.cs b/src/System.Web.Mvc/IViewEngine.cs
new file mode 100644
index 00000000..cda8075f
--- /dev/null
+++ b/src/System.Web.Mvc/IViewEngine.cs
@@ -0,0 +1,9 @@
+namespace System.Web.Mvc
+{
+ public interface IViewEngine
+ {
+ ViewEngineResult FindPartialView(ControllerContext controllerContext, string partialViewName, bool useCache);
+ ViewEngineResult FindView(ControllerContext controllerContext, string viewName, string masterName, bool useCache);
+ void ReleaseView(ControllerContext controllerContext, IView view);
+ }
+}
diff --git a/src/System.Web.Mvc/IViewLocationCache.cs b/src/System.Web.Mvc/IViewLocationCache.cs
new file mode 100644
index 00000000..a8661349
--- /dev/null
+++ b/src/System.Web.Mvc/IViewLocationCache.cs
@@ -0,0 +1,8 @@
+namespace System.Web.Mvc
+{
+ public interface IViewLocationCache
+ {
+ string GetViewLocation(HttpContextBase httpContext, string key);
+ void InsertViewLocation(HttpContextBase httpContext, string key, string virtualPath);
+ }
+}
diff --git a/src/System.Web.Mvc/IViewPageActivator.cs b/src/System.Web.Mvc/IViewPageActivator.cs
new file mode 100644
index 00000000..40420ff8
--- /dev/null
+++ b/src/System.Web.Mvc/IViewPageActivator.cs
@@ -0,0 +1,7 @@
+namespace System.Web.Mvc
+{
+ public interface IViewPageActivator
+ {
+ object Create(ControllerContext controllerContext, Type type);
+ }
+}
diff --git a/src/System.Web.Mvc/IViewStartPageChild.cs b/src/System.Web.Mvc/IViewStartPageChild.cs
new file mode 100644
index 00000000..b97d02ed
--- /dev/null
+++ b/src/System.Web.Mvc/IViewStartPageChild.cs
@@ -0,0 +1,9 @@
+namespace System.Web.Mvc
+{
+ internal interface IViewStartPageChild
+ {
+ HtmlHelper<object> Html { get; }
+ UrlHelper Url { get; }
+ ViewContext ViewContext { get; }
+ }
+}
diff --git a/src/System.Web.Mvc/InputType.cs b/src/System.Web.Mvc/InputType.cs
new file mode 100644
index 00000000..d3c3b204
--- /dev/null
+++ b/src/System.Web.Mvc/InputType.cs
@@ -0,0 +1,11 @@
+namespace System.Web.Mvc
+{
+ public enum InputType
+ {
+ CheckBox,
+ Hidden,
+ Password,
+ Radio,
+ Text
+ }
+}
diff --git a/src/System.Web.Mvc/JavaScriptResult.cs b/src/System.Web.Mvc/JavaScriptResult.cs
new file mode 100644
index 00000000..e692648d
--- /dev/null
+++ b/src/System.Web.Mvc/JavaScriptResult.cs
@@ -0,0 +1,23 @@
+namespace System.Web.Mvc
+{
+ public class JavaScriptResult : ActionResult
+ {
+ public string Script { get; set; }
+
+ public override void ExecuteResult(ControllerContext context)
+ {
+ if (context == null)
+ {
+ throw new ArgumentNullException("context");
+ }
+
+ HttpResponseBase response = context.HttpContext.Response;
+ response.ContentType = "application/x-javascript";
+
+ if (Script != null)
+ {
+ response.Write(Script);
+ }
+ }
+ }
+}
diff --git a/src/System.Web.Mvc/JsonRequestBehavior.cs b/src/System.Web.Mvc/JsonRequestBehavior.cs
new file mode 100644
index 00000000..438d3cb3
--- /dev/null
+++ b/src/System.Web.Mvc/JsonRequestBehavior.cs
@@ -0,0 +1,8 @@
+namespace System.Web.Mvc
+{
+ public enum JsonRequestBehavior
+ {
+ AllowGet,
+ DenyGet,
+ }
+}
diff --git a/src/System.Web.Mvc/JsonResult.cs b/src/System.Web.Mvc/JsonResult.cs
new file mode 100644
index 00000000..762a787c
--- /dev/null
+++ b/src/System.Web.Mvc/JsonResult.cs
@@ -0,0 +1,73 @@
+using System.Text;
+using System.Web.Mvc.Properties;
+using System.Web.Script.Serialization;
+
+namespace System.Web.Mvc
+{
+ public class JsonResult : ActionResult
+ {
+ public JsonResult()
+ {
+ JsonRequestBehavior = JsonRequestBehavior.DenyGet;
+ }
+
+ public Encoding ContentEncoding { get; set; }
+
+ public string ContentType { get; set; }
+
+ public object Data { get; set; }
+
+ public JsonRequestBehavior JsonRequestBehavior { get; set; }
+
+ /// <summary>
+ /// When set MaxJsonLength passed to the JavaScriptSerializer.
+ /// </summary>
+ public int? MaxJsonLength { get; set; }
+
+ /// <summary>
+ /// When set RecursionLimit passed to the JavaScriptSerializer.
+ /// </summary>
+ public int? RecursionLimit { get; set; }
+
+ public override void ExecuteResult(ControllerContext context)
+ {
+ if (context == null)
+ {
+ throw new ArgumentNullException("context");
+ }
+ if (JsonRequestBehavior == JsonRequestBehavior.DenyGet &&
+ String.Equals(context.HttpContext.Request.HttpMethod, "GET", StringComparison.OrdinalIgnoreCase))
+ {
+ throw new InvalidOperationException(MvcResources.JsonRequest_GetNotAllowed);
+ }
+
+ HttpResponseBase response = context.HttpContext.Response;
+
+ if (!String.IsNullOrEmpty(ContentType))
+ {
+ response.ContentType = ContentType;
+ }
+ else
+ {
+ response.ContentType = "application/json";
+ }
+ if (ContentEncoding != null)
+ {
+ response.ContentEncoding = ContentEncoding;
+ }
+ if (Data != null)
+ {
+ JavaScriptSerializer serializer = new JavaScriptSerializer();
+ if (MaxJsonLength.HasValue)
+ {
+ serializer.MaxJsonLength = MaxJsonLength.Value;
+ }
+ if (RecursionLimit.HasValue)
+ {
+ serializer.RecursionLimit = RecursionLimit.Value;
+ }
+ response.Write(serializer.Serialize(Data));
+ }
+ }
+ }
+}
diff --git a/src/System.Web.Mvc/JsonValueProviderFactory.cs b/src/System.Web.Mvc/JsonValueProviderFactory.cs
new file mode 100644
index 00000000..31be9845
--- /dev/null
+++ b/src/System.Web.Mvc/JsonValueProviderFactory.cs
@@ -0,0 +1,131 @@
+using System.Collections;
+using System.Collections.Generic;
+using System.Collections.Specialized;
+using System.Configuration;
+using System.Globalization;
+using System.IO;
+using System.Web.Mvc.Properties;
+using System.Web.Script.Serialization;
+
+namespace System.Web.Mvc
+{
+ public sealed class JsonValueProviderFactory : ValueProviderFactory
+ {
+ private static void AddToBackingStore(EntryLimitedDictionary backingStore, string prefix, object value)
+ {
+ IDictionary<string, object> d = value as IDictionary<string, object>;
+ if (d != null)
+ {
+ foreach (KeyValuePair<string, object> entry in d)
+ {
+ AddToBackingStore(backingStore, MakePropertyKey(prefix, entry.Key), entry.Value);
+ }
+ return;
+ }
+
+ IList l = value as IList;
+ if (l != null)
+ {
+ for (int i = 0; i < l.Count; i++)
+ {
+ AddToBackingStore(backingStore, MakeArrayKey(prefix, i), l[i]);
+ }
+ return;
+ }
+
+ // primitive
+ backingStore.Add(prefix, value);
+ }
+
+ private static object GetDeserializedObject(ControllerContext controllerContext)
+ {
+ if (!controllerContext.HttpContext.Request.ContentType.StartsWith("application/json", StringComparison.OrdinalIgnoreCase))
+ {
+ // not JSON request
+ return null;
+ }
+
+ StreamReader reader = new StreamReader(controllerContext.HttpContext.Request.InputStream);
+ string bodyText = reader.ReadToEnd();
+ if (String.IsNullOrEmpty(bodyText))
+ {
+ // no JSON data
+ return null;
+ }
+
+ JavaScriptSerializer serializer = new JavaScriptSerializer();
+ object jsonData = serializer.DeserializeObject(bodyText);
+ return jsonData;
+ }
+
+ public override IValueProvider GetValueProvider(ControllerContext controllerContext)
+ {
+ if (controllerContext == null)
+ {
+ throw new ArgumentNullException("controllerContext");
+ }
+
+ object jsonData = GetDeserializedObject(controllerContext);
+ if (jsonData == null)
+ {
+ return null;
+ }
+
+ Dictionary<string, object> backingStore = new Dictionary<string, object>(StringComparer.OrdinalIgnoreCase);
+ EntryLimitedDictionary backingStoreWrapper = new EntryLimitedDictionary(backingStore);
+ AddToBackingStore(backingStoreWrapper, String.Empty, jsonData);
+ return new DictionaryValueProvider<object>(backingStore, CultureInfo.CurrentCulture);
+ }
+
+ private static string MakeArrayKey(string prefix, int index)
+ {
+ return prefix + "[" + index.ToString(CultureInfo.InvariantCulture) + "]";
+ }
+
+ private static string MakePropertyKey(string prefix, string propertyName)
+ {
+ return (String.IsNullOrEmpty(prefix)) ? propertyName : prefix + "." + propertyName;
+ }
+
+ private class EntryLimitedDictionary
+ {
+ private static int _maximumDepth = GetMaximumDepth();
+ private readonly IDictionary<string, object> _innerDictionary;
+ private int _itemCount = 0;
+
+ public EntryLimitedDictionary(IDictionary<string, object> innerDictionary)
+ {
+ _innerDictionary = innerDictionary;
+ }
+
+ public void Add(string key, object value)
+ {
+ if (++_itemCount > _maximumDepth)
+ {
+ throw new InvalidOperationException(MvcResources.JsonValueProviderFactory_RequestTooLarge);
+ }
+
+ _innerDictionary.Add(key, value);
+ }
+
+ private static int GetMaximumDepth()
+ {
+ NameValueCollection appSettings = ConfigurationManager.AppSettings;
+ if (appSettings != null)
+ {
+ string[] valueArray = appSettings.GetValues("aspnet:MaxJsonDeserializerMembers");
+ if (valueArray != null && valueArray.Length > 0)
+ {
+ int result;
+ if (Int32.TryParse(valueArray[0], out result))
+ {
+ return result;
+ }
+ }
+ }
+
+ return 1000; // Fallback default
+ }
+ }
+ }
+}
diff --git a/src/System.Web.Mvc/LinqBinaryModelBinder.cs b/src/System.Web.Mvc/LinqBinaryModelBinder.cs
new file mode 100644
index 00000000..005a36df
--- /dev/null
+++ b/src/System.Web.Mvc/LinqBinaryModelBinder.cs
@@ -0,0 +1,18 @@
+using System.Data.Linq;
+
+namespace System.Web.Mvc
+{
+ public class LinqBinaryModelBinder : ByteArrayModelBinder
+ {
+ public override object BindModel(ControllerContext controllerContext, ModelBindingContext bindingContext)
+ {
+ byte[] byteValue = (byte[])base.BindModel(controllerContext, bindingContext);
+ if (byteValue == null)
+ {
+ return null;
+ }
+
+ return new Binary(byteValue);
+ }
+ }
+}
diff --git a/src/System.Web.Mvc/ModelBinderAttribute.cs b/src/System.Web.Mvc/ModelBinderAttribute.cs
new file mode 100644
index 00000000..4e517d3c
--- /dev/null
+++ b/src/System.Web.Mvc/ModelBinderAttribute.cs
@@ -0,0 +1,44 @@
+using System.Globalization;
+using System.Web.Mvc.Properties;
+
+namespace System.Web.Mvc
+{
+ [AttributeUsage(ValidTargets, AllowMultiple = false, Inherited = false)]
+ public sealed class ModelBinderAttribute : CustomModelBinderAttribute
+ {
+ public ModelBinderAttribute(Type binderType)
+ {
+ if (binderType == null)
+ {
+ throw new ArgumentNullException("binderType");
+ }
+ if (!typeof(IModelBinder).IsAssignableFrom(binderType))
+ {
+ string message = String.Format(CultureInfo.CurrentCulture,
+ MvcResources.ModelBinderAttribute_TypeNotIModelBinder, binderType.FullName);
+ throw new ArgumentException(message, "binderType");
+ }
+
+ BinderType = binderType;
+ }
+
+ public Type BinderType { get; private set; }
+
+ public override IModelBinder GetBinder()
+ {
+ try
+ {
+ return (IModelBinder)Activator.CreateInstance(BinderType);
+ }
+ catch (Exception ex)
+ {
+ throw new InvalidOperationException(
+ String.Format(
+ CultureInfo.CurrentCulture,
+ MvcResources.ModelBinderAttribute_ErrorCreatingModelBinder,
+ BinderType.FullName),
+ ex);
+ }
+ }
+ }
+}
diff --git a/src/System.Web.Mvc/ModelBinderDictionary.cs b/src/System.Web.Mvc/ModelBinderDictionary.cs
new file mode 100644
index 00000000..273787e5
--- /dev/null
+++ b/src/System.Web.Mvc/ModelBinderDictionary.cs
@@ -0,0 +1,167 @@
+using System.Collections;
+using System.Collections.Generic;
+using System.Globalization;
+using System.Web.Mvc.Properties;
+
+namespace System.Web.Mvc
+{
+ public class ModelBinderDictionary : IDictionary<Type, IModelBinder>
+ {
+ private readonly Dictionary<Type, IModelBinder> _innerDictionary = new Dictionary<Type, IModelBinder>();
+ private IModelBinder _defaultBinder;
+ private ModelBinderProviderCollection _modelBinderProviders;
+
+ public ModelBinderDictionary()
+ : this(ModelBinderProviders.BinderProviders)
+ {
+ }
+
+ internal ModelBinderDictionary(ModelBinderProviderCollection modelBinderProviders)
+ {
+ _modelBinderProviders = modelBinderProviders;
+ }
+
+ public int Count
+ {
+ get { return _innerDictionary.Count; }
+ }
+
+ public IModelBinder DefaultBinder
+ {
+ get
+ {
+ if (_defaultBinder == null)
+ {
+ _defaultBinder = new DefaultModelBinder();
+ }
+ return _defaultBinder;
+ }
+ set { _defaultBinder = value; }
+ }
+
+ public bool IsReadOnly
+ {
+ get { return ((IDictionary<Type, IModelBinder>)_innerDictionary).IsReadOnly; }
+ }
+
+ public ICollection<Type> Keys
+ {
+ get { return _innerDictionary.Keys; }
+ }
+
+ public ICollection<IModelBinder> Values
+ {
+ get { return _innerDictionary.Values; }
+ }
+
+ public IModelBinder this[Type key]
+ {
+ get
+ {
+ IModelBinder binder;
+ _innerDictionary.TryGetValue(key, out binder);
+ return binder;
+ }
+ set { _innerDictionary[key] = value; }
+ }
+
+ public void Add(KeyValuePair<Type, IModelBinder> item)
+ {
+ ((IDictionary<Type, IModelBinder>)_innerDictionary).Add(item);
+ }
+
+ public void Add(Type key, IModelBinder value)
+ {
+ _innerDictionary.Add(key, value);
+ }
+
+ public void Clear()
+ {
+ _innerDictionary.Clear();
+ }
+
+ public bool Contains(KeyValuePair<Type, IModelBinder> item)
+ {
+ return ((IDictionary<Type, IModelBinder>)_innerDictionary).Contains(item);
+ }
+
+ public bool ContainsKey(Type key)
+ {
+ return _innerDictionary.ContainsKey(key);
+ }
+
+ public void CopyTo(KeyValuePair<Type, IModelBinder>[] array, int arrayIndex)
+ {
+ ((IDictionary<Type, IModelBinder>)_innerDictionary).CopyTo(array, arrayIndex);
+ }
+
+ public IModelBinder GetBinder(Type modelType)
+ {
+ return GetBinder(modelType, true /* fallbackToDefault */);
+ }
+
+ public virtual IModelBinder GetBinder(Type modelType, bool fallbackToDefault)
+ {
+ if (modelType == null)
+ {
+ throw new ArgumentNullException("modelType");
+ }
+
+ return GetBinder(modelType, (fallbackToDefault) ? DefaultBinder : null);
+ }
+
+ private IModelBinder GetBinder(Type modelType, IModelBinder fallbackBinder)
+ {
+ // Try to look up a binder for this type. We use this order of precedence:
+ // 1. Binder returned from provider
+ // 2. Binder registered in the global table
+ // 3. Binder attribute defined on the type
+ // 4. Supplied fallback binder
+
+ IModelBinder binder = _modelBinderProviders.GetBinder(modelType);
+ if (binder != null)
+ {
+ return binder;
+ }
+
+ if (_innerDictionary.TryGetValue(modelType, out binder))
+ {
+ return binder;
+ }
+
+ binder = ModelBinders.GetBinderFromAttributes(modelType,
+ () => String.Format(CultureInfo.CurrentCulture, MvcResources.ModelBinderDictionary_MultipleAttributes, modelType.FullName));
+
+ return binder ?? fallbackBinder;
+ }
+
+ public IEnumerator<KeyValuePair<Type, IModelBinder>> GetEnumerator()
+ {
+ return _innerDictionary.GetEnumerator();
+ }
+
+ public bool Remove(KeyValuePair<Type, IModelBinder> item)
+ {
+ return ((IDictionary<Type, IModelBinder>)_innerDictionary).Remove(item);
+ }
+
+ public bool Remove(Type key)
+ {
+ return _innerDictionary.Remove(key);
+ }
+
+ public bool TryGetValue(Type key, out IModelBinder value)
+ {
+ return _innerDictionary.TryGetValue(key, out value);
+ }
+
+ #region IEnumerable Members
+
+ IEnumerator IEnumerable.GetEnumerator()
+ {
+ return ((IEnumerable)_innerDictionary).GetEnumerator();
+ }
+
+ #endregion
+ }
+}
diff --git a/src/System.Web.Mvc/ModelBinderProviderCollection.cs b/src/System.Web.Mvc/ModelBinderProviderCollection.cs
new file mode 100644
index 00000000..eb3117a3
--- /dev/null
+++ b/src/System.Web.Mvc/ModelBinderProviderCollection.cs
@@ -0,0 +1,66 @@
+using System.Collections.Generic;
+using System.Collections.ObjectModel;
+using System.Linq;
+
+namespace System.Web.Mvc
+{
+ public class ModelBinderProviderCollection : Collection<IModelBinderProvider>
+ {
+ private IResolver<IEnumerable<IModelBinderProvider>> _serviceResolver;
+
+ public ModelBinderProviderCollection()
+ {
+ _serviceResolver = new MultiServiceResolver<IModelBinderProvider>(() => Items);
+ }
+
+ public ModelBinderProviderCollection(IList<IModelBinderProvider> list)
+ : base(list)
+ {
+ _serviceResolver = new MultiServiceResolver<IModelBinderProvider>(() => Items);
+ }
+
+ internal ModelBinderProviderCollection(IResolver<IEnumerable<IModelBinderProvider>> resolver, params IModelBinderProvider[] providers)
+ : base(providers)
+ {
+ _serviceResolver = resolver ?? new MultiServiceResolver<IModelBinderProvider>(() => Items);
+ }
+
+ private IEnumerable<IModelBinderProvider> CombinedItems
+ {
+ get { return _serviceResolver.Current; }
+ }
+
+ protected override void InsertItem(int index, IModelBinderProvider item)
+ {
+ if (item == null)
+ {
+ throw new ArgumentNullException("item");
+ }
+ base.InsertItem(index, item);
+ }
+
+ protected override void SetItem(int index, IModelBinderProvider item)
+ {
+ if (item == null)
+ {
+ throw new ArgumentNullException("item");
+ }
+ base.SetItem(index, item);
+ }
+
+ public IModelBinder GetBinder(Type modelType)
+ {
+ if (modelType == null)
+ {
+ throw new ArgumentNullException("modelType");
+ }
+
+ var modelBinders = from providers in CombinedItems
+ let modelBinder = providers.GetBinder(modelType)
+ where modelBinder != null
+ select modelBinder;
+
+ return modelBinders.FirstOrDefault();
+ }
+ }
+}
diff --git a/src/System.Web.Mvc/ModelBinderProviders.cs b/src/System.Web.Mvc/ModelBinderProviders.cs
new file mode 100644
index 00000000..dcbed64e
--- /dev/null
+++ b/src/System.Web.Mvc/ModelBinderProviders.cs
@@ -0,0 +1,14 @@
+namespace System.Web.Mvc
+{
+ public static class ModelBinderProviders
+ {
+ private static readonly ModelBinderProviderCollection _binderProviders = new ModelBinderProviderCollection
+ {
+ };
+
+ public static ModelBinderProviderCollection BinderProviders
+ {
+ get { return _binderProviders; }
+ }
+ }
+}
diff --git a/src/System.Web.Mvc/ModelBinders.cs b/src/System.Web.Mvc/ModelBinders.cs
new file mode 100644
index 00000000..da986a42
--- /dev/null
+++ b/src/System.Web.Mvc/ModelBinders.cs
@@ -0,0 +1,71 @@
+using System.ComponentModel;
+using System.Data.Linq;
+using System.Linq;
+using System.Reflection;
+using System.Threading;
+
+namespace System.Web.Mvc
+{
+ public static class ModelBinders
+ {
+ private static readonly ModelBinderDictionary _binders = CreateDefaultBinderDictionary();
+
+ public static ModelBinderDictionary Binders
+ {
+ get { return _binders; }
+ }
+
+ internal static IModelBinder GetBinderFromAttributes(Type type, Func<string> errorMessageAccessor)
+ {
+ AttributeCollection allAttrs = TypeDescriptorHelper.Get(type).GetAttributes();
+ CustomModelBinderAttribute[] filteredAttrs = allAttrs.OfType<CustomModelBinderAttribute>().ToArray();
+ return GetBinderFromAttributesImpl(filteredAttrs, errorMessageAccessor);
+ }
+
+ internal static IModelBinder GetBinderFromAttributes(ICustomAttributeProvider element, Func<string> errorMessageAccessor)
+ {
+ CustomModelBinderAttribute[] attrs = (CustomModelBinderAttribute[])element.GetCustomAttributes(typeof(CustomModelBinderAttribute), true /* inherit */);
+ return GetBinderFromAttributesImpl(attrs, errorMessageAccessor);
+ }
+
+ private static IModelBinder GetBinderFromAttributesImpl(CustomModelBinderAttribute[] attrs, Func<string> errorMessageAccessor)
+ {
+ // this method is used to get a custom binder based on the attributes of the element passed to it.
+ // it will return null if a binder cannot be detected based on the attributes alone.
+
+ if (attrs == null)
+ {
+ return null;
+ }
+
+ switch (attrs.Length)
+ {
+ case 0:
+ return null;
+
+ case 1:
+ IModelBinder binder = attrs[0].GetBinder();
+ return binder;
+
+ default:
+ string errorMessage = errorMessageAccessor();
+ throw new InvalidOperationException(errorMessage);
+ }
+ }
+
+ private static ModelBinderDictionary CreateDefaultBinderDictionary()
+ {
+ // We can't add a binder to the HttpPostedFileBase type as an attribute, so we'll just
+ // prepopulate the dictionary as a convenience to users.
+
+ ModelBinderDictionary binders = new ModelBinderDictionary()
+ {
+ { typeof(HttpPostedFileBase), new HttpPostedFileBaseModelBinder() },
+ { typeof(byte[]), new ByteArrayModelBinder() },
+ { typeof(Binary), new LinqBinaryModelBinder() },
+ { typeof(CancellationToken), new CancellationTokenModelBinder() }
+ };
+ return binders;
+ }
+ }
+}
diff --git a/src/System.Web.Mvc/ModelBindingContext.cs b/src/System.Web.Mvc/ModelBindingContext.cs
new file mode 100644
index 00000000..e57be059
--- /dev/null
+++ b/src/System.Web.Mvc/ModelBindingContext.cs
@@ -0,0 +1,138 @@
+using System.Collections.Generic;
+using System.Diagnostics.CodeAnalysis;
+using System.Linq;
+using System.Web.Mvc.Properties;
+
+namespace System.Web.Mvc
+{
+ public class ModelBindingContext
+ {
+ private static readonly Predicate<string> _defaultPropertyFilter = _ => true;
+
+ private string _modelName;
+ private ModelStateDictionary _modelState;
+ private Predicate<string> _propertyFilter;
+ private Dictionary<string, ModelMetadata> _propertyMetadata;
+
+ public ModelBindingContext()
+ : this(null)
+ {
+ }
+
+ // copies certain values that won't change between parent and child objects,
+ // e.g. ValueProvider, ModelState
+ public ModelBindingContext(ModelBindingContext bindingContext)
+ {
+ if (bindingContext != null)
+ {
+ ModelState = bindingContext.ModelState;
+ ValueProvider = bindingContext.ValueProvider;
+ }
+ }
+
+ public bool FallbackToEmptyPrefix { get; set; }
+
+ [SuppressMessage("Microsoft.Usage", "CA1801:ReviewUnusedParameters", MessageId = "value", Justification = "Cannot remove setter as that's a breaking change")]
+ public object Model
+ {
+ get { return ModelMetadata.Model; }
+ set { throw new InvalidOperationException(MvcResources.ModelMetadata_PropertyNotSettable); }
+ }
+
+ public ModelMetadata ModelMetadata { get; set; }
+
+ public string ModelName
+ {
+ get
+ {
+ if (_modelName == null)
+ {
+ _modelName = String.Empty;
+ }
+ return _modelName;
+ }
+ set { _modelName = value; }
+ }
+
+ [SuppressMessage("Microsoft.Usage", "CA2227:CollectionPropertiesShouldBeReadOnly", Justification = "The containing type is mutable.")]
+ public ModelStateDictionary ModelState
+ {
+ get
+ {
+ if (_modelState == null)
+ {
+ _modelState = new ModelStateDictionary();
+ }
+ return _modelState;
+ }
+ set { _modelState = value; }
+ }
+
+ [SuppressMessage("Microsoft.Usage", "CA1801:ReviewUnusedParameters", MessageId = "value", Justification = "Cannot remove setter as that's a breaking change")]
+ public Type ModelType
+ {
+ get { return ModelMetadata.ModelType; }
+ set { throw new InvalidOperationException(MvcResources.ModelMetadata_PropertyNotSettable); }
+ }
+
+ public Predicate<string> PropertyFilter
+ {
+ get
+ {
+ if (_propertyFilter == null)
+ {
+ _propertyFilter = _defaultPropertyFilter;
+ }
+ return _propertyFilter;
+ }
+ set { _propertyFilter = value; }
+ }
+
+ public IDictionary<string, ModelMetadata> PropertyMetadata
+ {
+ get
+ {
+ if (_propertyMetadata == null)
+ {
+ _propertyMetadata = ModelMetadata.Properties.ToDictionary(m => m.PropertyName, StringComparer.OrdinalIgnoreCase);
+ }
+
+ return _propertyMetadata;
+ }
+ }
+
+ public IValueProvider ValueProvider { get; set; }
+
+ internal IUnvalidatedValueProvider UnvalidatedValueProvider
+ {
+ get { return (ValueProvider as IUnvalidatedValueProvider) ?? new UnvalidatedValueProviderWrapper(ValueProvider); }
+ }
+
+ // Used to wrap an IValueProvider in an IUnvalidatedValueProvider
+ private sealed class UnvalidatedValueProviderWrapper : IValueProvider, IUnvalidatedValueProvider
+ {
+ private readonly IValueProvider _backingProvider;
+
+ public UnvalidatedValueProviderWrapper(IValueProvider backingProvider)
+ {
+ _backingProvider = backingProvider;
+ }
+
+ public ValueProviderResult GetValue(string key, bool skipValidation)
+ {
+ // 'skipValidation' isn't understood by the backing provider and can be ignored
+ return GetValue(key);
+ }
+
+ public bool ContainsPrefix(string prefix)
+ {
+ return _backingProvider.ContainsPrefix(prefix);
+ }
+
+ public ValueProviderResult GetValue(string key)
+ {
+ return _backingProvider.GetValue(key);
+ }
+ }
+ }
+}
diff --git a/src/System.Web.Mvc/ModelError.cs b/src/System.Web.Mvc/ModelError.cs
new file mode 100644
index 00000000..d05f2509
--- /dev/null
+++ b/src/System.Web.Mvc/ModelError.cs
@@ -0,0 +1,31 @@
+namespace System.Web.Mvc
+{
+ [Serializable]
+ public class ModelError
+ {
+ public ModelError(Exception exception)
+ : this(exception, null /* errorMessage */)
+ {
+ }
+
+ public ModelError(Exception exception, string errorMessage)
+ : this(errorMessage)
+ {
+ if (exception == null)
+ {
+ throw new ArgumentNullException("exception");
+ }
+
+ Exception = exception;
+ }
+
+ public ModelError(string errorMessage)
+ {
+ ErrorMessage = errorMessage ?? String.Empty;
+ }
+
+ public Exception Exception { get; private set; }
+
+ public string ErrorMessage { get; private set; }
+ }
+}
diff --git a/src/System.Web.Mvc/ModelErrorCollection.cs b/src/System.Web.Mvc/ModelErrorCollection.cs
new file mode 100644
index 00000000..e6a7867d
--- /dev/null
+++ b/src/System.Web.Mvc/ModelErrorCollection.cs
@@ -0,0 +1,18 @@
+using System.Collections.ObjectModel;
+
+namespace System.Web.Mvc
+{
+ [Serializable]
+ public class ModelErrorCollection : Collection<ModelError>
+ {
+ public void Add(Exception exception)
+ {
+ Add(new ModelError(exception));
+ }
+
+ public void Add(string errorMessage)
+ {
+ Add(new ModelError(errorMessage));
+ }
+ }
+}
diff --git a/src/System.Web.Mvc/ModelMetadata.cs b/src/System.Web.Mvc/ModelMetadata.cs
new file mode 100644
index 00000000..4a2ad50c
--- /dev/null
+++ b/src/System.Web.Mvc/ModelMetadata.cs
@@ -0,0 +1,407 @@
+using System.Collections.Generic;
+using System.ComponentModel;
+using System.Diagnostics.CodeAnalysis;
+using System.Globalization;
+using System.Linq;
+using System.Linq.Expressions;
+using System.Reflection;
+using System.Web.Mvc.ExpressionUtil;
+using System.Web.Mvc.Properties;
+
+namespace System.Web.Mvc
+{
+ public class ModelMetadata
+ {
+ public const int DefaultOrder = 10000;
+
+ private readonly Type _containerType;
+ private readonly Type _modelType;
+ private readonly string _propertyName;
+
+ /// <summary>
+ /// Explicit backing store for the things we want initialized by default, so don't have to call
+ /// the protected virtual setters of an auto-generated property
+ /// </summary>
+ private Dictionary<string, object> _additionalValues = new Dictionary<string, object>(StringComparer.OrdinalIgnoreCase);
+ private bool _convertEmptyStringToNull = true;
+ private bool _isRequired;
+ private object _model;
+ private Func<object> _modelAccessor;
+ private int _order = DefaultOrder;
+ private IEnumerable<ModelMetadata> _properties;
+ private Type _realModelType;
+ private bool _requestValidationEnabled = true;
+ private bool _showForDisplay = true;
+ private bool _showForEdit = true;
+ private string _simpleDisplayText;
+
+ public ModelMetadata(ModelMetadataProvider provider, Type containerType, Func<object> modelAccessor, Type modelType, string propertyName)
+ {
+ if (provider == null)
+ {
+ throw new ArgumentNullException("provider");
+ }
+ if (modelType == null)
+ {
+ throw new ArgumentNullException("modelType");
+ }
+
+ Provider = provider;
+
+ _containerType = containerType;
+ _isRequired = !TypeHelpers.TypeAllowsNullValue(modelType);
+ _modelAccessor = modelAccessor;
+ _modelType = modelType;
+ _propertyName = propertyName;
+ }
+
+ public virtual Dictionary<string, object> AdditionalValues
+ {
+ get { return _additionalValues; }
+ }
+
+ public Type ContainerType
+ {
+ get { return _containerType; }
+ }
+
+ public virtual bool ConvertEmptyStringToNull
+ {
+ get { return _convertEmptyStringToNull; }
+ set { _convertEmptyStringToNull = value; }
+ }
+
+ public virtual string DataTypeName { get; set; }
+
+ public virtual string Description { get; set; }
+
+ public virtual string DisplayFormatString { get; set; }
+
+ [SuppressMessage("Microsoft.Naming", "CA1721:PropertyNamesShouldNotMatchGetMethods", Justification = "The method is a delegating helper to choose among multiple property values")]
+ public virtual string DisplayName { get; set; }
+
+ public virtual string EditFormatString { get; set; }
+
+ public virtual bool HideSurroundingHtml { get; set; }
+
+ public virtual bool IsComplexType
+ {
+ get { return !(TypeDescriptor.GetConverter(ModelType).CanConvertFrom(typeof(string))); }
+ }
+
+ public bool IsNullableValueType
+ {
+ get { return TypeHelpers.IsNullableValueType(ModelType); }
+ }
+
+ public virtual bool IsReadOnly { get; set; }
+
+ public virtual bool IsRequired
+ {
+ get { return _isRequired; }
+ set { _isRequired = value; }
+ }
+
+ public object Model
+ {
+ get
+ {
+ if (_modelAccessor != null)
+ {
+ _model = _modelAccessor();
+ _modelAccessor = null;
+ }
+ return _model;
+ }
+ set
+ {
+ _model = value;
+ _modelAccessor = null;
+ _properties = null;
+ _realModelType = null;
+ }
+ }
+
+ public Type ModelType
+ {
+ get { return _modelType; }
+ }
+
+ public virtual string NullDisplayText { get; set; }
+
+ public virtual int Order
+ {
+ get { return _order; }
+ set { _order = value; }
+ }
+
+ public virtual IEnumerable<ModelMetadata> Properties
+ {
+ get
+ {
+ if (_properties == null)
+ {
+ _properties = Provider.GetMetadataForProperties(Model, RealModelType).OrderBy(m => m.Order);
+ }
+ return _properties;
+ }
+ }
+
+ public string PropertyName
+ {
+ get { return _propertyName; }
+ }
+
+ protected ModelMetadataProvider Provider { get; set; }
+
+ internal Type RealModelType
+ {
+ get
+ {
+ if (_realModelType == null)
+ {
+ _realModelType = ModelType;
+
+ // Don't call GetType() if the model is Nullable<T>, because it will
+ // turn Nullable<T> into T for non-null values
+ if (Model != null && !TypeHelpers.IsNullableValueType(ModelType))
+ {
+ _realModelType = Model.GetType();
+ }
+ }
+
+ return _realModelType;
+ }
+ }
+
+ public virtual bool RequestValidationEnabled
+ {
+ get { return _requestValidationEnabled; }
+ set { _requestValidationEnabled = value; }
+ }
+
+ public virtual string ShortDisplayName { get; set; }
+
+ public virtual bool ShowForDisplay
+ {
+ get { return _showForDisplay; }
+ set { _showForDisplay = value; }
+ }
+
+ public virtual bool ShowForEdit
+ {
+ get { return _showForEdit; }
+ set { _showForEdit = value; }
+ }
+
+ [SuppressMessage("Microsoft.Naming", "CA1721:PropertyNamesShouldNotMatchGetMethods", Justification = "This property delegates to the method when the user has not yet set a simple display text value.")]
+ public virtual string SimpleDisplayText
+ {
+ get
+ {
+ if (_simpleDisplayText == null)
+ {
+ _simpleDisplayText = GetSimpleDisplayText();
+ }
+ return _simpleDisplayText;
+ }
+ set { _simpleDisplayText = value; }
+ }
+
+ public virtual string TemplateHint { get; set; }
+
+ public virtual string Watermark { get; set; }
+
+ [SuppressMessage("Microsoft.Design", "CA1006:DoNotNestGenericTypesInMemberSignatures", Justification = "This is an appropriate nesting of generic types")]
+ public static ModelMetadata FromLambdaExpression<TParameter, TValue>(Expression<Func<TParameter, TValue>> expression,
+ ViewDataDictionary<TParameter> viewData)
+ {
+ return FromLambdaExpression(expression, viewData, metadataProvider: null);
+ }
+
+ internal static ModelMetadata FromLambdaExpression<TParameter, TValue>(Expression<Func<TParameter, TValue>> expression,
+ ViewDataDictionary<TParameter> viewData,
+ ModelMetadataProvider metadataProvider)
+ {
+ if (expression == null)
+ {
+ throw new ArgumentNullException("expression");
+ }
+ if (viewData == null)
+ {
+ throw new ArgumentNullException("viewData");
+ }
+
+ string propertyName = null;
+ Type containerType = null;
+ bool legalExpression = false;
+
+ // Need to verify the expression is valid; it needs to at least end in something
+ // that we can convert to a meaningful string for model binding purposes
+
+ switch (expression.Body.NodeType)
+ {
+ case ExpressionType.ArrayIndex:
+ // ArrayIndex always means a single-dimensional indexer; multi-dimensional indexer is a method call to Get()
+ legalExpression = true;
+ break;
+
+ case ExpressionType.Call:
+ // Only legal method call is a single argument indexer/DefaultMember call
+ legalExpression = ExpressionHelper.IsSingleArgumentIndexer(expression.Body);
+ break;
+
+ case ExpressionType.MemberAccess:
+ // Property/field access is always legal
+ MemberExpression memberExpression = (MemberExpression)expression.Body;
+ propertyName = memberExpression.Member is PropertyInfo ? memberExpression.Member.Name : null;
+ containerType = memberExpression.Expression.Type;
+ legalExpression = true;
+ break;
+
+ case ExpressionType.Parameter:
+ // Parameter expression means "model => model", so we delegate to FromModel
+ return FromModel(viewData, metadataProvider);
+ }
+
+ if (!legalExpression)
+ {
+ throw new InvalidOperationException(MvcResources.TemplateHelpers_TemplateLimitations);
+ }
+
+ TParameter container = viewData.Model;
+ Func<object> modelAccessor = () =>
+ {
+ try
+ {
+ return CachedExpressionCompiler.Process(expression)(container);
+ }
+ catch (NullReferenceException)
+ {
+ return null;
+ }
+ };
+
+ return GetMetadataFromProvider(modelAccessor, typeof(TValue), propertyName, containerType, metadataProvider);
+ }
+
+ private static ModelMetadata FromModel(ViewDataDictionary viewData, ModelMetadataProvider metadataProvider)
+ {
+ return viewData.ModelMetadata ?? GetMetadataFromProvider(null, typeof(string), null, null, metadataProvider);
+ }
+
+ public static ModelMetadata FromStringExpression(string expression, ViewDataDictionary viewData)
+ {
+ return FromStringExpression(expression, viewData, metadataProvider: null);
+ }
+
+ internal static ModelMetadata FromStringExpression(string expression, ViewDataDictionary viewData, ModelMetadataProvider metadataProvider)
+ {
+ if (expression == null)
+ {
+ throw new ArgumentNullException("expression");
+ }
+ if (viewData == null)
+ {
+ throw new ArgumentNullException("viewData");
+ }
+ if (expression.Length == 0)
+ {
+ // Empty string really means "model metadata for the current model"
+ return FromModel(viewData, metadataProvider);
+ }
+
+ ViewDataInfo vdi = viewData.GetViewDataInfo(expression);
+ Type containerType = null;
+ Type modelType = null;
+ Func<object> modelAccessor = null;
+ string propertyName = null;
+
+ if (vdi != null)
+ {
+ if (vdi.Container != null)
+ {
+ containerType = vdi.Container.GetType();
+ }
+
+ modelAccessor = () => vdi.Value;
+
+ if (vdi.PropertyDescriptor != null)
+ {
+ propertyName = vdi.PropertyDescriptor.Name;
+ modelType = vdi.PropertyDescriptor.PropertyType;
+ }
+ else if (vdi.Value != null)
+ {
+ // We only need to delay accessing properties (for LINQ to SQL)
+ modelType = vdi.Value.GetType();
+ }
+ }
+ else if (viewData.ModelMetadata != null)
+ {
+ // Try getting a property from ModelMetadata if we couldn't find an answer in ViewData
+ ModelMetadata propertyMetadata = viewData.ModelMetadata.Properties.Where(p => p.PropertyName == expression).FirstOrDefault();
+ if (propertyMetadata != null)
+ {
+ return propertyMetadata;
+ }
+ }
+
+ return GetMetadataFromProvider(modelAccessor, modelType ?? typeof(string), propertyName, containerType, metadataProvider);
+ }
+
+ [SuppressMessage("Microsoft.Design", "CA1024:UsePropertiesWhereAppropriate", Justification = "The method is a delegating helper to choose among multiple property values")]
+ public string GetDisplayName()
+ {
+ return DisplayName ?? PropertyName ?? ModelType.Name;
+ }
+
+ private static ModelMetadata GetMetadataFromProvider(Func<object> modelAccessor, Type modelType, string propertyName, Type containerType, ModelMetadataProvider metadataProvider)
+ {
+ metadataProvider = metadataProvider ?? ModelMetadataProviders.Current;
+ if (containerType != null && !String.IsNullOrEmpty(propertyName))
+ {
+ return metadataProvider.GetMetadataForProperty(modelAccessor, containerType, propertyName);
+ }
+ return metadataProvider.GetMetadataForType(modelAccessor, modelType);
+ }
+
+ [SuppressMessage("Microsoft.Design", "CA1024:UsePropertiesWhereAppropriate", Justification = "This method is used to resolve the simple display text when it was not explicitly set through other means.")]
+ protected virtual string GetSimpleDisplayText()
+ {
+ if (Model == null)
+ {
+ return NullDisplayText;
+ }
+
+ string toStringResult = Convert.ToString(Model, CultureInfo.CurrentCulture);
+ if (toStringResult == null)
+ {
+ return String.Empty;
+ }
+
+ if (!toStringResult.Equals(Model.GetType().FullName, StringComparison.Ordinal))
+ {
+ return toStringResult;
+ }
+
+ ModelMetadata firstProperty = Properties.FirstOrDefault();
+ if (firstProperty == null)
+ {
+ return String.Empty;
+ }
+
+ if (firstProperty.Model == null)
+ {
+ return firstProperty.NullDisplayText;
+ }
+
+ return Convert.ToString(firstProperty.Model, CultureInfo.CurrentCulture);
+ }
+
+ public virtual IEnumerable<ModelValidator> GetValidators(ControllerContext context)
+ {
+ return ModelValidatorProviders.Providers.GetValidators(this, context);
+ }
+ }
+}
diff --git a/src/System.Web.Mvc/ModelMetadataProvider.cs b/src/System.Web.Mvc/ModelMetadataProvider.cs
new file mode 100644
index 00000000..0db73fba
--- /dev/null
+++ b/src/System.Web.Mvc/ModelMetadataProvider.cs
@@ -0,0 +1,13 @@
+using System.Collections.Generic;
+
+namespace System.Web.Mvc
+{
+ public abstract class 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);
+ }
+}
diff --git a/src/System.Web.Mvc/ModelMetadataProviders.cs b/src/System.Web.Mvc/ModelMetadataProviders.cs
new file mode 100644
index 00000000..d9cd8b92
--- /dev/null
+++ b/src/System.Web.Mvc/ModelMetadataProviders.cs
@@ -0,0 +1,29 @@
+namespace System.Web.Mvc
+{
+ public class ModelMetadataProviders
+ {
+ private static ModelMetadataProviders _instance = new ModelMetadataProviders();
+ private ModelMetadataProvider _currentProvider;
+ private IResolver<ModelMetadataProvider> _resolver;
+
+ internal ModelMetadataProviders(IResolver<ModelMetadataProvider> resolver = null)
+ {
+ _resolver = resolver ?? new SingleServiceResolver<ModelMetadataProvider>(
+ () => _currentProvider,
+ new CachedDataAnnotationsModelMetadataProvider(),
+ "ModelMetadataProviders.Current");
+ }
+
+ public static ModelMetadataProvider Current
+ {
+ get { return _instance.CurrentInternal; }
+ set { _instance.CurrentInternal = value; }
+ }
+
+ internal ModelMetadataProvider CurrentInternal
+ {
+ get { return _resolver.Current; }
+ set { _currentProvider = value ?? new EmptyModelMetadataProvider(); }
+ }
+ }
+}
diff --git a/src/System.Web.Mvc/ModelState.cs b/src/System.Web.Mvc/ModelState.cs
new file mode 100644
index 00000000..ae1d28d4
--- /dev/null
+++ b/src/System.Web.Mvc/ModelState.cs
@@ -0,0 +1,15 @@
+namespace System.Web.Mvc
+{
+ [Serializable]
+ public class ModelState
+ {
+ private ModelErrorCollection _errors = new ModelErrorCollection();
+
+ public ValueProviderResult Value { get; set; }
+
+ public ModelErrorCollection Errors
+ {
+ get { return _errors; }
+ }
+ }
+}
diff --git a/src/System.Web.Mvc/ModelStateDictionary.cs b/src/System.Web.Mvc/ModelStateDictionary.cs
new file mode 100644
index 00000000..f1b149f2
--- /dev/null
+++ b/src/System.Web.Mvc/ModelStateDictionary.cs
@@ -0,0 +1,180 @@
+using System.Collections;
+using System.Collections.Generic;
+using System.Linq;
+
+namespace System.Web.Mvc
+{
+ [Serializable]
+ public class ModelStateDictionary : IDictionary<string, ModelState>
+ {
+ private readonly Dictionary<string, ModelState> _innerDictionary = new Dictionary<string, ModelState>(StringComparer.OrdinalIgnoreCase);
+
+ public ModelStateDictionary()
+ {
+ }
+
+ public ModelStateDictionary(ModelStateDictionary dictionary)
+ {
+ if (dictionary == null)
+ {
+ throw new ArgumentNullException("dictionary");
+ }
+
+ foreach (var entry in dictionary)
+ {
+ _innerDictionary.Add(entry.Key, entry.Value);
+ }
+ }
+
+ public int Count
+ {
+ get { return _innerDictionary.Count; }
+ }
+
+ public bool IsReadOnly
+ {
+ get { return ((IDictionary<string, ModelState>)_innerDictionary).IsReadOnly; }
+ }
+
+ public bool IsValid
+ {
+ get { return Values.All(modelState => modelState.Errors.Count == 0); }
+ }
+
+ public ICollection<string> Keys
+ {
+ get { return _innerDictionary.Keys; }
+ }
+
+ public ICollection<ModelState> Values
+ {
+ get { return _innerDictionary.Values; }
+ }
+
+ public ModelState this[string key]
+ {
+ get
+ {
+ ModelState value;
+ _innerDictionary.TryGetValue(key, out value);
+ return value;
+ }
+ set { _innerDictionary[key] = value; }
+ }
+
+ public void Add(KeyValuePair<string, ModelState> item)
+ {
+ ((IDictionary<string, ModelState>)_innerDictionary).Add(item);
+ }
+
+ public void Add(string key, ModelState value)
+ {
+ _innerDictionary.Add(key, value);
+ }
+
+ public void AddModelError(string key, Exception exception)
+ {
+ GetModelStateForKey(key).Errors.Add(exception);
+ }
+
+ public void AddModelError(string key, string errorMessage)
+ {
+ GetModelStateForKey(key).Errors.Add(errorMessage);
+ }
+
+ public void Clear()
+ {
+ _innerDictionary.Clear();
+ }
+
+ public bool Contains(KeyValuePair<string, ModelState> item)
+ {
+ return ((IDictionary<string, ModelState>)_innerDictionary).Contains(item);
+ }
+
+ public bool ContainsKey(string key)
+ {
+ return _innerDictionary.ContainsKey(key);
+ }
+
+ public void CopyTo(KeyValuePair<string, ModelState>[] array, int arrayIndex)
+ {
+ ((IDictionary<string, ModelState>)_innerDictionary).CopyTo(array, arrayIndex);
+ }
+
+ public IEnumerator<KeyValuePair<string, ModelState>> GetEnumerator()
+ {
+ return _innerDictionary.GetEnumerator();
+ }
+
+ private ModelState GetModelStateForKey(string key)
+ {
+ if (key == null)
+ {
+ throw new ArgumentNullException("key");
+ }
+
+ ModelState modelState;
+ if (!TryGetValue(key, out modelState))
+ {
+ modelState = new ModelState();
+ this[key] = modelState;
+ }
+
+ return modelState;
+ }
+
+ public bool IsValidField(string key)
+ {
+ if (key == null)
+ {
+ throw new ArgumentNullException("key");
+ }
+
+ // if the key is not found in the dictionary, we just say that it's valid (since there are no errors)
+ return DictionaryHelpers.FindKeysWithPrefix(this, key).All(entry => entry.Value.Errors.Count == 0);
+ }
+
+ public void Merge(ModelStateDictionary dictionary)
+ {
+ if (dictionary == null)
+ {
+ return;
+ }
+
+ foreach (var entry in dictionary)
+ {
+ this[entry.Key] = entry.Value;
+ }
+ }
+
+ public bool Remove(KeyValuePair<string, ModelState> item)
+ {
+ return ((IDictionary<string, ModelState>)_innerDictionary).Remove(item);
+ }
+
+ public bool Remove(string key)
+ {
+ return _innerDictionary.Remove(key);
+ }
+
+ public void SetModelValue(string key, ValueProviderResult value)
+ {
+ GetModelStateForKey(key).Value = value;
+ }
+
+ public bool TryGetValue(string key, out ModelState value)
+ {
+ return _innerDictionary.TryGetValue(key, out value);
+ }
+
+ #region IEnumerable Members
+
+ IEnumerator IEnumerable.GetEnumerator()
+ {
+ return ((IEnumerable)_innerDictionary).GetEnumerator();
+ }
+
+ #endregion
+ }
+}
diff --git a/src/System.Web.Mvc/ModelValidationResult.cs b/src/System.Web.Mvc/ModelValidationResult.cs
new file mode 100644
index 00000000..f7815371
--- /dev/null
+++ b/src/System.Web.Mvc/ModelValidationResult.cs
@@ -0,0 +1,20 @@
+namespace System.Web.Mvc
+{
+ public class ModelValidationResult
+ {
+ private string _memberName;
+ private string _message;
+
+ public string MemberName
+ {
+ get { return _memberName ?? String.Empty; }
+ set { _memberName = value; }
+ }
+
+ public string Message
+ {
+ get { return _message ?? String.Empty; }
+ set { _message = value; }
+ }
+ }
+}
diff --git a/src/System.Web.Mvc/ModelValidator.cs b/src/System.Web.Mvc/ModelValidator.cs
new file mode 100644
index 00000000..e5c9774c
--- /dev/null
+++ b/src/System.Web.Mvc/ModelValidator.cs
@@ -0,0 +1,86 @@
+using System.Collections.Generic;
+using System.Diagnostics.CodeAnalysis;
+using System.Linq;
+
+namespace System.Web.Mvc
+{
+ public abstract class ModelValidator
+ {
+ protected ModelValidator(ModelMetadata metadata, ControllerContext controllerContext)
+ {
+ if (metadata == null)
+ {
+ throw new ArgumentNullException("metadata");
+ }
+ if (controllerContext == null)
+ {
+ throw new ArgumentNullException("controllerContext");
+ }
+
+ Metadata = metadata;
+ ControllerContext = controllerContext;
+ }
+
+ protected internal ControllerContext ControllerContext { get; private set; }
+
+ public virtual bool IsRequired
+ {
+ get { return false; }
+ }
+
+ protected internal ModelMetadata Metadata { get; private set; }
+
+ [SuppressMessage("Microsoft.Design", "CA1024:UsePropertiesWhereAppropriate", Justification = "This method may perform non-trivial work.")]
+ public virtual IEnumerable<ModelClientValidationRule> GetClientValidationRules()
+ {
+ return Enumerable.Empty<ModelClientValidationRule>();
+ }
+
+ public static ModelValidator GetModelValidator(ModelMetadata metadata, ControllerContext context)
+ {
+ return new CompositeModelValidator(metadata, context);
+ }
+
+ public abstract IEnumerable<ModelValidationResult> Validate(object container);
+
+ private class CompositeModelValidator : ModelValidator
+ {
+ public CompositeModelValidator(ModelMetadata metadata, ControllerContext controllerContext)
+ : base(metadata, controllerContext)
+ {
+ }
+
+ public override IEnumerable<ModelValidationResult> Validate(object container)
+ {
+ bool propertiesValid = true;
+
+ foreach (ModelMetadata propertyMetadata in Metadata.Properties)
+ {
+ foreach (ModelValidator propertyValidator in propertyMetadata.GetValidators(ControllerContext))
+ {
+ foreach (ModelValidationResult propertyResult in propertyValidator.Validate(Metadata.Model))
+ {
+ propertiesValid = false;
+ yield return new ModelValidationResult
+ {
+ MemberName = DefaultModelBinder.CreateSubPropertyName(propertyMetadata.PropertyName, propertyResult.MemberName),
+ Message = propertyResult.Message
+ };
+ }
+ }
+ }
+
+ if (propertiesValid)
+ {
+ foreach (ModelValidator typeValidator in Metadata.GetValidators(ControllerContext))
+ {
+ foreach (ModelValidationResult typeResult in typeValidator.Validate(container))
+ {
+ yield return typeResult;
+ }
+ }
+ }
+ }
+ }
+ }
+}
diff --git a/src/System.Web.Mvc/ModelValidatorProvider.cs b/src/System.Web.Mvc/ModelValidatorProvider.cs
new file mode 100644
index 00000000..932a091f
--- /dev/null
+++ b/src/System.Web.Mvc/ModelValidatorProvider.cs
@@ -0,0 +1,9 @@
+using System.Collections.Generic;
+
+namespace System.Web.Mvc
+{
+ public abstract class ModelValidatorProvider
+ {
+ public abstract IEnumerable<ModelValidator> GetValidators(ModelMetadata metadata, ControllerContext context);
+ }
+}
diff --git a/src/System.Web.Mvc/ModelValidatorProviderCollection.cs b/src/System.Web.Mvc/ModelValidatorProviderCollection.cs
new file mode 100644
index 00000000..666fdbf0
--- /dev/null
+++ b/src/System.Web.Mvc/ModelValidatorProviderCollection.cs
@@ -0,0 +1,56 @@
+using System.Collections.Generic;
+using System.Collections.ObjectModel;
+using System.Linq;
+
+namespace System.Web.Mvc
+{
+ public class ModelValidatorProviderCollection : Collection<ModelValidatorProvider>
+ {
+ private IResolver<IEnumerable<ModelValidatorProvider>> _serviceResolver;
+
+ public ModelValidatorProviderCollection()
+ {
+ _serviceResolver = new MultiServiceResolver<ModelValidatorProvider>(() => Items);
+ }
+
+ public ModelValidatorProviderCollection(IList<ModelValidatorProvider> list)
+ : base(list)
+ {
+ _serviceResolver = new MultiServiceResolver<ModelValidatorProvider>(() => Items);
+ }
+
+ internal ModelValidatorProviderCollection(IResolver<IEnumerable<ModelValidatorProvider>> serviceResolver, params ModelValidatorProvider[] validatorProvidors)
+ : base(validatorProvidors)
+ {
+ _serviceResolver = serviceResolver ?? new MultiServiceResolver<ModelValidatorProvider>(() => Items);
+ }
+
+ private IEnumerable<ModelValidatorProvider> CombinedItems
+ {
+ get { return _serviceResolver.Current; }
+ }
+
+ protected override void InsertItem(int index, ModelValidatorProvider item)
+ {
+ if (item == null)
+ {
+ throw new ArgumentNullException("item");
+ }
+ base.InsertItem(index, item);
+ }
+
+ protected override void SetItem(int index, ModelValidatorProvider item)
+ {
+ if (item == null)
+ {
+ throw new ArgumentNullException("item");
+ }
+ base.SetItem(index, item);
+ }
+
+ public IEnumerable<ModelValidator> GetValidators(ModelMetadata metadata, ControllerContext context)
+ {
+ return CombinedItems.SelectMany(provider => provider.GetValidators(metadata, context));
+ }
+ }
+}
diff --git a/src/System.Web.Mvc/ModelValidatorProviders.cs b/src/System.Web.Mvc/ModelValidatorProviders.cs
new file mode 100644
index 00000000..66852578
--- /dev/null
+++ b/src/System.Web.Mvc/ModelValidatorProviders.cs
@@ -0,0 +1,17 @@
+namespace System.Web.Mvc
+{
+ public static class ModelValidatorProviders
+ {
+ private static readonly ModelValidatorProviderCollection _providers = new ModelValidatorProviderCollection()
+ {
+ new DataAnnotationsModelValidatorProvider(),
+ new DataErrorInfoModelValidatorProvider(),
+ new ClientDataTypeModelValidatorProvider()
+ };
+
+ public static ModelValidatorProviderCollection Providers
+ {
+ get { return _providers; }
+ }
+ }
+}
diff --git a/src/System.Web.Mvc/MultiSelectList.cs b/src/System.Web.Mvc/MultiSelectList.cs
new file mode 100644
index 00000000..eedb0100
--- /dev/null
+++ b/src/System.Web.Mvc/MultiSelectList.cs
@@ -0,0 +1,118 @@
+using System.Collections;
+using System.Collections.Generic;
+using System.Diagnostics.CodeAnalysis;
+using System.Globalization;
+using System.Linq;
+using System.Web.UI;
+
+namespace System.Web.Mvc
+{
+ [SuppressMessage("Microsoft.Naming", "CA1704:IdentifiersShouldBeSpelledCorrectly", MessageId = "Multi", Justification = "FxCop won't accept this in the custom dictionary, so we're suppressing it in source")]
+ [SuppressMessage("Microsoft.Naming", "CA1710:IdentifiersShouldHaveCorrectSuffix", Justification = "This is a shipped API")]
+ public class MultiSelectList : IEnumerable<SelectListItem>
+ {
+ public MultiSelectList(IEnumerable items)
+ : this(items, null /* selectedValues */)
+ {
+ }
+
+ public MultiSelectList(IEnumerable items, IEnumerable selectedValues)
+ : this(items, null /* dataValuefield */, null /* dataTextField */, selectedValues)
+ {
+ }
+
+ public MultiSelectList(IEnumerable items, string dataValueField, string dataTextField)
+ : this(items, dataValueField, dataTextField, null /* selectedValues */)
+ {
+ }
+
+ public MultiSelectList(IEnumerable items, string dataValueField, string dataTextField, IEnumerable selectedValues)
+ {
+ if (items == null)
+ {
+ throw new ArgumentNullException("items");
+ }
+
+ Items = items;
+ DataValueField = dataValueField;
+ DataTextField = dataTextField;
+ SelectedValues = selectedValues;
+ }
+
+ public string DataTextField { get; private set; }
+
+ public string DataValueField { get; private set; }
+
+ public IEnumerable Items { get; private set; }
+
+ public IEnumerable SelectedValues { get; private set; }
+
+ public virtual IEnumerator<SelectListItem> GetEnumerator()
+ {
+ return GetListItems().GetEnumerator();
+ }
+
+ internal IList<SelectListItem> GetListItems()
+ {
+ return (!String.IsNullOrEmpty(DataValueField))
+ ? GetListItemsWithValueField()
+ : GetListItemsWithoutValueField();
+ }
+
+ private IList<SelectListItem> GetListItemsWithValueField()
+ {
+ HashSet<string> selectedValues = new HashSet<string>(StringComparer.OrdinalIgnoreCase);
+ if (SelectedValues != null)
+ {
+ selectedValues.UnionWith(from object value in SelectedValues
+ select Convert.ToString(value, CultureInfo.CurrentCulture));
+ }
+
+ var listItems = from object item in Items
+ let value = Eval(item, DataValueField)
+ select new SelectListItem
+ {
+ Value = value,
+ Text = Eval(item, DataTextField),
+ Selected = selectedValues.Contains(value)
+ };
+ return listItems.ToList();
+ }
+
+ private IList<SelectListItem> GetListItemsWithoutValueField()
+ {
+ HashSet<object> selectedValues = new HashSet<object>();
+ if (SelectedValues != null)
+ {
+ selectedValues.UnionWith(SelectedValues.Cast<object>());
+ }
+
+ var listItems = from object item in Items
+ select new SelectListItem
+ {
+ Text = Eval(item, DataTextField),
+ Selected = selectedValues.Contains(item)
+ };
+ return listItems.ToList();
+ }
+
+ private static string Eval(object container, string expression)
+ {
+ object value = container;
+ if (!String.IsNullOrEmpty(expression))
+ {
+ value = DataBinder.Eval(container, expression);
+ }
+ return Convert.ToString(value, CultureInfo.CurrentCulture);
+ }
+
+ #region IEnumerable Members
+
+ IEnumerator IEnumerable.GetEnumerator()
+ {
+ return GetEnumerator();
+ }
+
+ #endregion
+ }
+}
diff --git a/src/System.Web.Mvc/MultiServiceResolver.cs b/src/System.Web.Mvc/MultiServiceResolver.cs
new file mode 100644
index 00000000..d3506fa2
--- /dev/null
+++ b/src/System.Web.Mvc/MultiServiceResolver.cs
@@ -0,0 +1,51 @@
+using System.Collections.Generic;
+using System.Linq;
+
+namespace System.Web.Mvc
+{
+ internal class MultiServiceResolver<TService> : IResolver<IEnumerable<TService>>
+ where TService : class
+ {
+ private IEnumerable<TService> _itemsFromService;
+ private Func<IEnumerable<TService>> _itemsThunk;
+ private Func<IDependencyResolver> _resolverThunk;
+
+ public MultiServiceResolver(Func<IEnumerable<TService>> itemsThunk)
+ {
+ if (itemsThunk == null)
+ {
+ throw new ArgumentNullException("itemsThunk");
+ }
+
+ _itemsThunk = itemsThunk;
+ _resolverThunk = () => DependencyResolver.Current;
+ }
+
+ internal MultiServiceResolver(Func<IEnumerable<TService>> itemsThunk, IDependencyResolver resolver)
+ : this(itemsThunk)
+ {
+ if (resolver != null)
+ {
+ _resolverThunk = () => resolver;
+ }
+ }
+
+ public IEnumerable<TService> Current
+ {
+ get
+ {
+ if (_itemsFromService == null)
+ {
+ lock (_itemsThunk)
+ {
+ if (_itemsFromService == null)
+ {
+ _itemsFromService = _resolverThunk().GetServices<TService>();
+ }
+ }
+ }
+ return _itemsFromService.Concat(_itemsThunk());
+ }
+ }
+ }
+}
diff --git a/src/System.Web.Mvc/MvcFilter.cs b/src/System.Web.Mvc/MvcFilter.cs
new file mode 100644
index 00000000..480b66ac
--- /dev/null
+++ b/src/System.Web.Mvc/MvcFilter.cs
@@ -0,0 +1,19 @@
+namespace System.Web.Mvc
+{
+ public abstract class MvcFilter : IMvcFilter
+ {
+ protected MvcFilter()
+ {
+ }
+
+ protected MvcFilter(bool allowMultiple, int order)
+ {
+ AllowMultiple = allowMultiple;
+ Order = order;
+ }
+
+ public bool AllowMultiple { get; private set; }
+
+ public int Order { get; private set; }
+ }
+}
diff --git a/src/System.Web.Mvc/MvcHandler.cs b/src/System.Web.Mvc/MvcHandler.cs
new file mode 100644
index 00000000..de4a6d8b
--- /dev/null
+++ b/src/System.Web.Mvc/MvcHandler.cs
@@ -0,0 +1,248 @@
+using System.Globalization;
+using System.Linq;
+using System.Reflection;
+using System.Threading;
+using System.Web.Mvc.Async;
+using System.Web.Mvc.Properties;
+using System.Web.Routing;
+using System.Web.SessionState;
+using Microsoft.Web.Infrastructure.DynamicValidationHelper;
+
+namespace System.Web.Mvc
+{
+ public class MvcHandler : IHttpAsyncHandler, IHttpHandler, IRequiresSessionState
+ {
+ private static readonly object _processRequestTag = new object();
+
+ internal static readonly string MvcVersion = GetMvcVersionString();
+ public static readonly string MvcVersionHeaderName = "X-AspNetMvc-Version";
+ private ControllerBuilder _controllerBuilder;
+
+ public MvcHandler(RequestContext requestContext)
+ {
+ if (requestContext == null)
+ {
+ throw new ArgumentNullException("requestContext");
+ }
+
+ RequestContext = requestContext;
+ }
+
+ internal ControllerBuilder ControllerBuilder
+ {
+ get
+ {
+ if (_controllerBuilder == null)
+ {
+ _controllerBuilder = ControllerBuilder.Current;
+ }
+ return _controllerBuilder;
+ }
+ set { _controllerBuilder = value; }
+ }
+
+ public static bool DisableMvcResponseHeader { get; set; }
+
+ protected virtual bool IsReusable
+ {
+ get { return false; }
+ }
+
+ public RequestContext RequestContext { get; private set; }
+
+ protected internal virtual void AddVersionHeader(HttpContextBase httpContext)
+ {
+ if (!DisableMvcResponseHeader)
+ {
+ httpContext.Response.AppendHeader(MvcVersionHeaderName, MvcVersion);
+ }
+ }
+
+ protected virtual IAsyncResult BeginProcessRequest(HttpContext httpContext, AsyncCallback callback, object state)
+ {
+ HttpContextBase httpContextBase = new HttpContextWrapper(httpContext);
+ return BeginProcessRequest(httpContextBase, callback, state);
+ }
+
+ protected internal virtual IAsyncResult BeginProcessRequest(HttpContextBase httpContext, AsyncCallback callback, object state)
+ {
+ return SecurityUtil.ProcessInApplicationTrust(() =>
+ {
+ IController controller;
+ IControllerFactory factory;
+ ProcessRequestInit(httpContext, out controller, out factory);
+
+ IAsyncController asyncController = controller as IAsyncController;
+ if (asyncController != null)
+ {
+ // asynchronous controller
+ BeginInvokeDelegate beginDelegate = delegate(AsyncCallback asyncCallback, object asyncState)
+ {
+ try
+ {
+ return asyncController.BeginExecute(RequestContext, asyncCallback, asyncState);
+ }
+ catch
+ {
+ factory.ReleaseController(asyncController);
+ throw;
+ }
+ };
+
+ EndInvokeDelegate endDelegate = delegate(IAsyncResult asyncResult)
+ {
+ try
+ {
+ asyncController.EndExecute(asyncResult);
+ }
+ finally
+ {
+ factory.ReleaseController(asyncController);
+ }
+ };
+
+ SynchronizationContext syncContext = SynchronizationContextUtil.GetSynchronizationContext();
+ AsyncCallback newCallback = AsyncUtil.WrapCallbackForSynchronizedExecution(callback, syncContext);
+ return AsyncResultWrapper.Begin(newCallback, state, beginDelegate, endDelegate, _processRequestTag);
+ }
+ else
+ {
+ // synchronous controller
+ Action action = delegate
+ {
+ try
+ {
+ controller.Execute(RequestContext);
+ }
+ finally
+ {
+ factory.ReleaseController(controller);
+ }
+ };
+
+ return AsyncResultWrapper.BeginSynchronous(callback, state, action, _processRequestTag);
+ }
+ });
+ }
+
+ protected internal virtual void EndProcessRequest(IAsyncResult asyncResult)
+ {
+ SecurityUtil.ProcessInApplicationTrust(() =>
+ {
+ AsyncResultWrapper.End(asyncResult, _processRequestTag);
+ });
+ }
+
+ private static string GetMvcVersionString()
+ {
+ // DevDiv 216459:
+ // This code originally used Assembly.GetName(), but that requires FileIOPermission, which isn't granted in
+ // medium trust. However, Assembly.FullName *is* accessible in medium trust.
+ return new AssemblyName(typeof(MvcHandler).Assembly.FullName).Version.ToString(2);
+ }
+
+ protected virtual void ProcessRequest(HttpContext httpContext)
+ {
+ HttpContextBase httpContextBase = new HttpContextWrapper(httpContext);
+ ProcessRequest(httpContextBase);
+ }
+
+ protected internal virtual void ProcessRequest(HttpContextBase httpContext)
+ {
+ SecurityUtil.ProcessInApplicationTrust(() =>
+ {
+ IController controller;
+ IControllerFactory factory;
+ ProcessRequestInit(httpContext, out controller, out factory);
+
+ try
+ {
+ controller.Execute(RequestContext);
+ }
+ finally
+ {
+ factory.ReleaseController(controller);
+ }
+ });
+ }
+
+ private void ProcessRequestInit(HttpContextBase httpContext, out IController controller, out IControllerFactory factory)
+ {
+ // If request validation has already been enabled, make it lazy. This allows attributes like [HttpPost] (which looks
+ // at Request.Form) to work correctly without triggering full validation.
+ // Tolerate null HttpContext for testing.
+ HttpContext currentContext = HttpContext.Current;
+ if (currentContext != null)
+ {
+ bool? isRequestValidationEnabled = ValidationUtility.IsValidationEnabled(currentContext);
+ if (isRequestValidationEnabled == true)
+ {
+ ValidationUtility.EnableDynamicValidation(currentContext);
+ }
+ }
+
+ AddVersionHeader(httpContext);
+ RemoveOptionalRoutingParameters();
+
+ // Get the controller type
+ string controllerName = RequestContext.RouteData.GetRequiredString("controller");
+
+ // Instantiate the controller and call Execute
+ factory = ControllerBuilder.GetControllerFactory();
+ controller = factory.CreateController(RequestContext, controllerName);
+ if (controller == null)
+ {
+ throw new InvalidOperationException(
+ String.Format(
+ CultureInfo.CurrentCulture,
+ MvcResources.ControllerBuilder_FactoryReturnedNull,
+ factory.GetType(),
+ controllerName));
+ }
+ }
+
+ private void RemoveOptionalRoutingParameters()
+ {
+ RouteValueDictionary rvd = RequestContext.RouteData.Values;
+
+ // Get all keys for which the corresponding value is 'Optional'.
+ // ToArray() necessary so that we don't manipulate the dictionary while enumerating.
+ string[] matchingKeys = (from entry in rvd
+ where entry.Value == UrlParameter.Optional
+ select entry.Key).ToArray();
+
+ foreach (string key in matchingKeys)
+ {
+ rvd.Remove(key);
+ }
+ }
+
+ #region IHttpHandler Members
+
+ bool IHttpHandler.IsReusable
+ {
+ get { return IsReusable; }
+ }
+
+ void IHttpHandler.ProcessRequest(HttpContext httpContext)
+ {
+ ProcessRequest(httpContext);
+ }
+
+ #endregion
+
+ #region IHttpAsyncHandler Members
+
+ IAsyncResult IHttpAsyncHandler.BeginProcessRequest(HttpContext context, AsyncCallback cb, object extraData)
+ {
+ return BeginProcessRequest(context, cb, extraData);
+ }
+
+ void IHttpAsyncHandler.EndProcessRequest(IAsyncResult result)
+ {
+ EndProcessRequest(result);
+ }
+
+ #endregion
+ }
+}
diff --git a/src/System.Web.Mvc/MvcHtmlString.cs b/src/System.Web.Mvc/MvcHtmlString.cs
new file mode 100644
index 00000000..8905cd04
--- /dev/null
+++ b/src/System.Web.Mvc/MvcHtmlString.cs
@@ -0,0 +1,28 @@
+using System.Diagnostics.CodeAnalysis;
+
+namespace System.Web.Mvc
+{
+ public sealed class MvcHtmlString : HtmlString
+ {
+ [SuppressMessage("Microsoft.Security", "CA2104:DoNotDeclareReadOnlyMutableReferenceTypes", Justification = "MvcHtmlString is immutable")]
+ public static readonly MvcHtmlString Empty = Create(String.Empty);
+
+ private readonly string _value;
+
+ public MvcHtmlString(string value)
+ : base(value ?? String.Empty)
+ {
+ _value = value ?? String.Empty;
+ }
+
+ public static MvcHtmlString Create(string value)
+ {
+ return new MvcHtmlString(value);
+ }
+
+ public static bool IsNullOrEmpty(MvcHtmlString value)
+ {
+ return (value == null || value._value.Length == 0);
+ }
+ }
+}
diff --git a/src/System.Web.Mvc/MvcHttpHandler.cs b/src/System.Web.Mvc/MvcHttpHandler.cs
new file mode 100644
index 00000000..a64fb272
--- /dev/null
+++ b/src/System.Web.Mvc/MvcHttpHandler.cs
@@ -0,0 +1,105 @@
+using System.Web.Mvc.Async;
+using System.Web.Routing;
+using System.Web.SessionState;
+
+namespace System.Web.Mvc
+{
+ public class MvcHttpHandler : UrlRoutingHandler, IHttpAsyncHandler, IRequiresSessionState
+ {
+ private static readonly object _processRequestTag = new object();
+
+ protected virtual IAsyncResult BeginProcessRequest(HttpContext httpContext, AsyncCallback callback, object state)
+ {
+ HttpContextBase httpContextBase = new HttpContextWrapper(httpContext);
+ return BeginProcessRequest(httpContextBase, callback, state);
+ }
+
+ protected internal virtual IAsyncResult BeginProcessRequest(HttpContextBase httpContext, AsyncCallback callback, object state)
+ {
+ IHttpHandler httpHandler = GetHttpHandler(httpContext);
+ IHttpAsyncHandler httpAsyncHandler = httpHandler as IHttpAsyncHandler;
+
+ if (httpAsyncHandler != null)
+ {
+ // asynchronous handler
+ BeginInvokeDelegate beginDelegate = delegate(AsyncCallback asyncCallback, object asyncState)
+ {
+ return httpAsyncHandler.BeginProcessRequest(HttpContext.Current, asyncCallback, asyncState);
+ };
+ EndInvokeDelegate endDelegate = delegate(IAsyncResult asyncResult)
+ {
+ httpAsyncHandler.EndProcessRequest(asyncResult);
+ };
+ return AsyncResultWrapper.Begin(callback, state, beginDelegate, endDelegate, _processRequestTag);
+ }
+ else
+ {
+ // synchronous handler
+ Action action = delegate
+ {
+ httpHandler.ProcessRequest(HttpContext.Current);
+ };
+ return AsyncResultWrapper.BeginSynchronous(callback, state, action, _processRequestTag);
+ }
+ }
+
+ protected internal virtual void EndProcessRequest(IAsyncResult asyncResult)
+ {
+ AsyncResultWrapper.End(asyncResult, _processRequestTag);
+ }
+
+ private static IHttpHandler GetHttpHandler(HttpContextBase httpContext)
+ {
+ DummyHttpHandler dummyHandler = new DummyHttpHandler();
+ dummyHandler.PublicProcessRequest(httpContext);
+ return dummyHandler.HttpHandler;
+ }
+
+ // synchronous code
+ protected override void VerifyAndProcessRequest(IHttpHandler httpHandler, HttpContextBase httpContext)
+ {
+ if (httpHandler == null)
+ {
+ throw new ArgumentNullException("httpHandler");
+ }
+
+ httpHandler.ProcessRequest(HttpContext.Current);
+ }
+
+ #region IHttpAsyncHandler Members
+
+ IAsyncResult IHttpAsyncHandler.BeginProcessRequest(HttpContext context, AsyncCallback cb, object extraData)
+ {
+ return BeginProcessRequest(context, cb, extraData);
+ }
+
+ void IHttpAsyncHandler.EndProcessRequest(IAsyncResult result)
+ {
+ EndProcessRequest(result);
+ }
+
+ #endregion
+
+ // Since UrlRoutingHandler.ProcessRequest() does the heavy lifting of looking at the RouteCollection for
+ // a matching route, we need to call into it. However, that method is also responsible for kicking off
+ // the synchronous request, and we can't allow it to do that. The purpose of this dummy class is to run
+ // only the lookup portion of UrlRoutingHandler.ProcessRequest(), then intercept the handler it returns
+ // and execute it asynchronously.
+
+ private sealed class DummyHttpHandler : UrlRoutingHandler
+ {
+ public IHttpHandler HttpHandler;
+
+ public void PublicProcessRequest(HttpContextBase httpContext)
+ {
+ ProcessRequest(httpContext);
+ }
+
+ protected override void VerifyAndProcessRequest(IHttpHandler httpHandler, HttpContextBase httpContext)
+ {
+ // don't process the request, just store a reference to it
+ HttpHandler = httpHandler;
+ }
+ }
+ }
+}
diff --git a/src/System.Web.Mvc/MvcRouteHandler.cs b/src/System.Web.Mvc/MvcRouteHandler.cs
new file mode 100644
index 00000000..4b898b2c
--- /dev/null
+++ b/src/System.Web.Mvc/MvcRouteHandler.cs
@@ -0,0 +1,47 @@
+using System.Web.Mvc.Properties;
+using System.Web.Routing;
+using System.Web.SessionState;
+
+namespace System.Web.Mvc
+{
+ public class MvcRouteHandler : IRouteHandler
+ {
+ private IControllerFactory _controllerFactory;
+
+ public MvcRouteHandler()
+ {
+ }
+
+ public MvcRouteHandler(IControllerFactory controllerFactory)
+ {
+ _controllerFactory = controllerFactory;
+ }
+
+ protected virtual IHttpHandler GetHttpHandler(RequestContext requestContext)
+ {
+ requestContext.HttpContext.SetSessionStateBehavior(GetSessionStateBehavior(requestContext));
+ return new MvcHandler(requestContext);
+ }
+
+ protected virtual SessionStateBehavior GetSessionStateBehavior(RequestContext requestContext)
+ {
+ string controllerName = (string)requestContext.RouteData.Values["controller"];
+ if (String.IsNullOrWhiteSpace(controllerName))
+ {
+ throw new InvalidOperationException(MvcResources.MvcRouteHandler_RouteValuesHasNoController);
+ }
+
+ IControllerFactory controllerFactory = _controllerFactory ?? ControllerBuilder.Current.GetControllerFactory();
+ return controllerFactory.GetControllerSessionBehavior(requestContext, controllerName);
+ }
+
+ #region IRouteHandler Members
+
+ IHttpHandler IRouteHandler.GetHttpHandler(RequestContext requestContext)
+ {
+ return GetHttpHandler(requestContext);
+ }
+
+ #endregion
+ }
+}
diff --git a/src/System.Web.Mvc/MvcWebRazorHostFactory.cs b/src/System.Web.Mvc/MvcWebRazorHostFactory.cs
new file mode 100644
index 00000000..f26f01ac
--- /dev/null
+++ b/src/System.Web.Mvc/MvcWebRazorHostFactory.cs
@@ -0,0 +1,20 @@
+using System.Web.Mvc.Razor;
+using System.Web.WebPages.Razor;
+
+namespace System.Web.Mvc
+{
+ public class MvcWebRazorHostFactory : WebRazorHostFactory
+ {
+ public override WebPageRazorHost CreateHost(string virtualPath, string physicalPath)
+ {
+ WebPageRazorHost host = base.CreateHost(virtualPath, physicalPath);
+
+ if (!host.IsSpecialPage)
+ {
+ return new MvcWebPageRazorHost(virtualPath, physicalPath);
+ }
+
+ return host;
+ }
+ }
+}
diff --git a/src/System.Web.Mvc/NameValueCollectionExtensions.cs b/src/System.Web.Mvc/NameValueCollectionExtensions.cs
new file mode 100644
index 00000000..31bdd4f3
--- /dev/null
+++ b/src/System.Web.Mvc/NameValueCollectionExtensions.cs
@@ -0,0 +1,33 @@
+using System.Collections.Generic;
+using System.Collections.Specialized;
+
+namespace System.Web.Mvc
+{
+ public static class NameValueCollectionExtensions
+ {
+ public static void CopyTo(this NameValueCollection collection, IDictionary<string, object> destination)
+ {
+ CopyTo(collection, destination, false /* replaceEntries */);
+ }
+
+ public static void CopyTo(this NameValueCollection collection, IDictionary<string, object> destination, bool replaceEntries)
+ {
+ if (collection == null)
+ {
+ throw new ArgumentNullException("collection");
+ }
+ if (destination == null)
+ {
+ throw new ArgumentNullException("destination");
+ }
+
+ foreach (string key in collection.Keys)
+ {
+ if (replaceEntries || !destination.ContainsKey(key))
+ {
+ destination[key] = collection[key];
+ }
+ }
+ }
+ }
+}
diff --git a/src/System.Web.Mvc/NameValueCollectionValueProvider.cs b/src/System.Web.Mvc/NameValueCollectionValueProvider.cs
new file mode 100644
index 00000000..0db898e2
--- /dev/null
+++ b/src/System.Web.Mvc/NameValueCollectionValueProvider.cs
@@ -0,0 +1,121 @@
+using System.Collections.Generic;
+using System.Collections.Specialized;
+using System.Globalization;
+using System.Threading;
+
+namespace System.Web.Mvc
+{
+ public class NameValueCollectionValueProvider : IValueProvider, IUnvalidatedValueProvider, IEnumerableValueProvider
+ {
+ private readonly HashSet<string> _prefixes = new HashSet<string>(StringComparer.OrdinalIgnoreCase);
+ private readonly Dictionary<string, ValueProviderResultPlaceholder> _values = new Dictionary<string, ValueProviderResultPlaceholder>(StringComparer.OrdinalIgnoreCase);
+
+ public NameValueCollectionValueProvider(NameValueCollection collection, CultureInfo culture)
+ : this(collection, null /* unvalidatedCollection */, culture)
+ {
+ }
+
+ public NameValueCollectionValueProvider(NameValueCollection collection, NameValueCollection unvalidatedCollection, CultureInfo culture)
+ {
+ if (collection == null)
+ {
+ throw new ArgumentNullException("collection");
+ }
+
+ AddValues(collection, unvalidatedCollection ?? collection, culture);
+ }
+
+ private void AddValues(NameValueCollection validatedCollection, NameValueCollection unvalidatedCollection, CultureInfo culture)
+ {
+ // Need to read keys from the unvalidated collection, as M.W.I's granular request validation is a bit touchy
+ // and validated entries at the time the key or value is looked at. For example, GetKey() will throw if the
+ // value fails request validation, even though the value's not being looked at (M.W.I can't tell the difference).
+
+ if (unvalidatedCollection.Count > 0)
+ {
+ _prefixes.Add(String.Empty);
+ }
+
+ foreach (string key in unvalidatedCollection)
+ {
+ if (key != null)
+ {
+ _prefixes.UnionWith(ValueProviderUtil.GetPrefixes(key));
+
+ // need to look up values lazily, as eagerly looking at the collection might trigger validation
+ _values[key] = new ValueProviderResultPlaceholder(key, validatedCollection, unvalidatedCollection, culture);
+ }
+ }
+ }
+
+ public virtual bool ContainsPrefix(string prefix)
+ {
+ if (prefix == null)
+ {
+ throw new ArgumentNullException("prefix");
+ }
+
+ return _prefixes.Contains(prefix);
+ }
+
+ public virtual ValueProviderResult GetValue(string key)
+ {
+ return GetValue(key, skipValidation: false);
+ }
+
+ public virtual ValueProviderResult GetValue(string key, bool skipValidation)
+ {
+ if (key == null)
+ {
+ throw new ArgumentNullException("key");
+ }
+
+ ValueProviderResultPlaceholder placeholder;
+ _values.TryGetValue(key, out placeholder);
+ if (placeholder == null)
+ {
+ return null;
+ }
+ else
+ {
+ return (skipValidation) ? placeholder.UnvalidatedResult : placeholder.ValidatedResult;
+ }
+ }
+
+ public virtual IDictionary<string, string> GetKeysFromPrefix(string prefix)
+ {
+ return ValueProviderUtil.GetKeysFromPrefix(_prefixes, prefix);
+ }
+
+ // Placeholder that can store a validated (in relation to request validation) or unvalidated
+ // ValueProviderResult for a given key.
+ private sealed class ValueProviderResultPlaceholder
+ {
+ private readonly Lazy<ValueProviderResult> _validatedResultPlaceholder;
+ private readonly Lazy<ValueProviderResult> _unvalidatedResultPlaceholder;
+
+ public ValueProviderResultPlaceholder(string key, NameValueCollection validatedCollection, NameValueCollection unvalidatedCollection, CultureInfo culture)
+ {
+ _validatedResultPlaceholder = new Lazy<ValueProviderResult>(() => GetResultFromCollection(key, validatedCollection, culture), LazyThreadSafetyMode.None);
+ _unvalidatedResultPlaceholder = new Lazy<ValueProviderResult>(() => GetResultFromCollection(key, unvalidatedCollection, culture), LazyThreadSafetyMode.None);
+ }
+
+ public ValueProviderResult ValidatedResult
+ {
+ get { return _validatedResultPlaceholder.Value; }
+ }
+
+ public ValueProviderResult UnvalidatedResult
+ {
+ get { return _unvalidatedResultPlaceholder.Value; }
+ }
+
+ private static ValueProviderResult GetResultFromCollection(string key, NameValueCollection collection, CultureInfo culture)
+ {
+ string[] rawValue = collection.GetValues(key);
+ string attemptedValue = collection[key];
+ return new ValueProviderResult(rawValue, attemptedValue, culture);
+ }
+ }
+ }
+}
diff --git a/src/System.Web.Mvc/NoAsyncTimeoutAttribute.cs b/src/System.Web.Mvc/NoAsyncTimeoutAttribute.cs
new file mode 100644
index 00000000..05da36c4
--- /dev/null
+++ b/src/System.Web.Mvc/NoAsyncTimeoutAttribute.cs
@@ -0,0 +1,13 @@
+using System.Threading;
+
+namespace System.Web.Mvc
+{
+ [AttributeUsage(AttributeTargets.Class | AttributeTargets.Method, Inherited = true, AllowMultiple = false)]
+ public sealed class NoAsyncTimeoutAttribute : AsyncTimeoutAttribute
+ {
+ public NoAsyncTimeoutAttribute()
+ : base(Timeout.Infinite)
+ {
+ }
+ }
+}
diff --git a/src/System.Web.Mvc/NonActionAttribute.cs b/src/System.Web.Mvc/NonActionAttribute.cs
new file mode 100644
index 00000000..be4e1d47
--- /dev/null
+++ b/src/System.Web.Mvc/NonActionAttribute.cs
@@ -0,0 +1,13 @@
+using System.Reflection;
+
+namespace System.Web.Mvc
+{
+ [AttributeUsage(AttributeTargets.Method, AllowMultiple = false, Inherited = true)]
+ public sealed class NonActionAttribute : ActionMethodSelectorAttribute
+ {
+ public override bool IsValidForRequest(ControllerContext controllerContext, MethodInfo methodInfo)
+ {
+ return false;
+ }
+ }
+}
diff --git a/src/System.Web.Mvc/NullViewLocationCache.cs b/src/System.Web.Mvc/NullViewLocationCache.cs
new file mode 100644
index 00000000..681e7e1e
--- /dev/null
+++ b/src/System.Web.Mvc/NullViewLocationCache.cs
@@ -0,0 +1,18 @@
+namespace System.Web.Mvc
+{
+ internal sealed class NullViewLocationCache : IViewLocationCache
+ {
+ #region IViewLocationCache Members
+
+ public string GetViewLocation(HttpContextBase httpContext, string key)
+ {
+ return null;
+ }
+
+ public void InsertViewLocation(HttpContextBase httpContext, string key, string virtualPath)
+ {
+ }
+
+ #endregion
+ }
+}
diff --git a/src/System.Web.Mvc/OutputCacheAttribute.cs b/src/System.Web.Mvc/OutputCacheAttribute.cs
new file mode 100644
index 00000000..f205f49a
--- /dev/null
+++ b/src/System.Web.Mvc/OutputCacheAttribute.cs
@@ -0,0 +1,355 @@
+using System.Collections.Generic;
+using System.Diagnostics.CodeAnalysis;
+using System.Globalization;
+using System.IO;
+using System.Linq;
+using System.Runtime.Caching;
+using System.Security.Cryptography;
+using System.Text;
+using System.Web.Mvc.Properties;
+using System.Web.UI;
+
+namespace System.Web.Mvc
+{
+ [SuppressMessage("Microsoft.Performance", "CA1813:AvoidUnsealedAttributes", Justification = "Unsealed so that subclassed types can set properties in the default constructor.")]
+ [AttributeUsage(AttributeTargets.Class | AttributeTargets.Method, Inherited = true, AllowMultiple = false)]
+ public class OutputCacheAttribute : ActionFilterAttribute, IExceptionFilter
+ {
+ private const string CacheKeyPrefix = "_MvcChildActionCache_";
+ private static ObjectCache _childActionCache;
+ private static object _childActionFilterFinishCallbackKey = new object();
+ private OutputCacheParameters _cacheSettings = new OutputCacheParameters { VaryByParam = "*" };
+ private Func<ObjectCache> _childActionCacheThunk = () => ChildActionCache;
+ private bool _locationWasSet;
+ private bool _noStoreWasSet;
+
+ public OutputCacheAttribute()
+ {
+ }
+
+ internal OutputCacheAttribute(ObjectCache childActionCache)
+ {
+ _childActionCacheThunk = () => childActionCache;
+ }
+
+ public string CacheProfile
+ {
+ get { return _cacheSettings.CacheProfile ?? String.Empty; }
+ set { _cacheSettings.CacheProfile = value; }
+ }
+
+ internal OutputCacheParameters CacheSettings
+ {
+ get { return _cacheSettings; }
+ }
+
+ public static ObjectCache ChildActionCache
+ {
+ get { return _childActionCache ?? MemoryCache.Default; }
+ set { _childActionCache = value; }
+ }
+
+ private ObjectCache ChildActionCacheInternal
+ {
+ get { return _childActionCacheThunk(); }
+ }
+
+ public int Duration
+ {
+ get { return _cacheSettings.Duration; }
+ set { _cacheSettings.Duration = value; }
+ }
+
+ public OutputCacheLocation Location
+ {
+ get { return _cacheSettings.Location; }
+ set
+ {
+ _cacheSettings.Location = value;
+ _locationWasSet = true;
+ }
+ }
+
+ public bool NoStore
+ {
+ get { return _cacheSettings.NoStore; }
+ set
+ {
+ _cacheSettings.NoStore = value;
+ _noStoreWasSet = true;
+ }
+ }
+
+ public string SqlDependency
+ {
+ get { return _cacheSettings.SqlDependency ?? String.Empty; }
+ set { _cacheSettings.SqlDependency = value; }
+ }
+
+ public string VaryByContentEncoding
+ {
+ get { return _cacheSettings.VaryByContentEncoding ?? String.Empty; }
+ set { _cacheSettings.VaryByContentEncoding = value; }
+ }
+
+ public string VaryByCustom
+ {
+ get { return _cacheSettings.VaryByCustom ?? String.Empty; }
+ set { _cacheSettings.VaryByCustom = value; }
+ }
+
+ public string VaryByHeader
+ {
+ get { return _cacheSettings.VaryByHeader ?? String.Empty; }
+ set { _cacheSettings.VaryByHeader = value; }
+ }
+
+ [SuppressMessage("Microsoft.Naming", "CA1704:IdentifiersShouldBeSpelledCorrectly", MessageId = "Param", Justification = "Matches the @ OutputCache page directive. Suppressed in source because this is a special case suppression.")]
+ public string VaryByParam
+ {
+ get { return _cacheSettings.VaryByParam ?? String.Empty; }
+ set { _cacheSettings.VaryByParam = value; }
+ }
+
+ private static void ClearChildActionFilterFinishCallback(ControllerContext controllerContext)
+ {
+ controllerContext.HttpContext.Items.Remove(_childActionFilterFinishCallbackKey);
+ }
+
+ private static void CompleteChildAction(ControllerContext filterContext, bool wasException)
+ {
+ Action<bool> callback = GetChildActionFilterFinishCallback(filterContext);
+
+ if (callback != null)
+ {
+ ClearChildActionFilterFinishCallback(filterContext);
+ callback(wasException);
+ }
+ }
+
+ private static Action<bool> GetChildActionFilterFinishCallback(ControllerContext controllerContext)
+ {
+ return controllerContext.HttpContext.Items[_childActionFilterFinishCallbackKey] as Action<bool>;
+ }
+
+ internal string GetChildActionUniqueId(ActionExecutingContext filterContext)
+ {
+ StringBuilder uniqueIdBuilder = new StringBuilder();
+
+ // Start with a prefix, presuming that we share the cache with other users
+ uniqueIdBuilder.Append(CacheKeyPrefix);
+
+ // Unique ID of the action description
+ uniqueIdBuilder.Append(filterContext.ActionDescriptor.UniqueId);
+
+ // Unique ID from the VaryByCustom settings, if any
+ uniqueIdBuilder.Append(DescriptorUtil.CreateUniqueId(VaryByCustom));
+ if (!String.IsNullOrEmpty(VaryByCustom))
+ {
+ string varyByCustomResult = filterContext.HttpContext.ApplicationInstance.GetVaryByCustomString(HttpContext.Current, VaryByCustom);
+ uniqueIdBuilder.Append(varyByCustomResult);
+ }
+
+ // Unique ID from the VaryByParam settings, if any
+ uniqueIdBuilder.Append(GetUniqueIdFromActionParameters(filterContext, SplitVaryByParam(VaryByParam)));
+
+ // The key is typically too long to be useful, so we use a cryptographic hash
+ // as the actual key (better randomization and key distribution, so small vary
+ // values will generate dramtically different keys).
+ using (SHA256 sha = SHA256.Create())
+ {
+ return Convert.ToBase64String(sha.ComputeHash(Encoding.UTF8.GetBytes(uniqueIdBuilder.ToString())));
+ }
+ }
+
+ private static string GetUniqueIdFromActionParameters(ActionExecutingContext filterContext, IEnumerable<string> keys)
+ {
+ // Generate a unique ID of normalized key names + key values
+ var keyValues = new Dictionary<string, object>(filterContext.ActionParameters, StringComparer.OrdinalIgnoreCase);
+ keys = (keys ?? keyValues.Keys).Select(key => key.ToUpperInvariant())
+ .OrderBy(key => key, StringComparer.Ordinal);
+
+ return DescriptorUtil.CreateUniqueId(keys.Concat(keys.Select(key => keyValues.ContainsKey(key) ? keyValues[key] : null)));
+ }
+
+ public static bool IsChildActionCacheActive(ControllerContext controllerContext)
+ {
+ return GetChildActionFilterFinishCallback(controllerContext) != null;
+ }
+
+ public override void OnActionExecuted(ActionExecutedContext filterContext)
+ {
+ if (filterContext == null)
+ {
+ throw new ArgumentNullException("filterContext");
+ }
+
+ // Complete the request if the child action threw an exception
+ if (filterContext.IsChildAction && filterContext.Exception != null)
+ {
+ CompleteChildAction(filterContext, wasException: true);
+ }
+ }
+
+ public override void OnActionExecuting(ActionExecutingContext filterContext)
+ {
+ if (filterContext == null)
+ {
+ throw new ArgumentNullException("filterContext");
+ }
+
+ if (filterContext.IsChildAction)
+ {
+ ValidateChildActionConfiguration();
+
+ // Already actively being captured? (i.e., cached child action inside of cached child action)
+ // Realistically, this needs write substitution to do properly (including things like authentication)
+ if (GetChildActionFilterFinishCallback(filterContext) != null)
+ {
+ throw new InvalidOperationException(MvcResources.OutputCacheAttribute_CannotNestChildCache);
+ }
+
+ // Already cached?
+ string uniqueId = GetChildActionUniqueId(filterContext);
+ string cachedValue = ChildActionCacheInternal.Get(uniqueId) as string;
+ if (cachedValue != null)
+ {
+ filterContext.Result = new ContentResult() { Content = cachedValue };
+ return;
+ }
+
+ // Swap in a new TextWriter so we can capture the output
+ StringWriter cachingWriter = new StringWriter(CultureInfo.InvariantCulture);
+ TextWriter originalWriter = filterContext.HttpContext.Response.Output;
+ filterContext.HttpContext.Response.Output = cachingWriter;
+
+ // Set a finish callback to clean up
+ SetChildActionFilterFinishCallback(filterContext, wasException =>
+ {
+ // Restore original writer
+ filterContext.HttpContext.Response.Output = originalWriter;
+
+ // Grab output and write it
+ string capturedText = cachingWriter.ToString();
+ filterContext.HttpContext.Response.Write(capturedText);
+
+ // Only cache output if this wasn't an error
+ if (!wasException)
+ {
+ ChildActionCacheInternal.Add(uniqueId, capturedText, DateTimeOffset.UtcNow.AddSeconds(Duration));
+ }
+ });
+ }
+ }
+
+ public void OnException(ExceptionContext filterContext)
+ {
+ if (filterContext == null)
+ {
+ throw new ArgumentNullException("filterContext");
+ }
+
+ if (filterContext.IsChildAction)
+ {
+ CompleteChildAction(filterContext, wasException: true);
+ }
+ }
+
+ public override void OnResultExecuting(ResultExecutingContext filterContext)
+ {
+ if (filterContext == null)
+ {
+ throw new ArgumentNullException("filterContext");
+ }
+
+ if (!filterContext.IsChildAction)
+ {
+ // we need to call ProcessRequest() since there's no other way to set the Page.Response intrinsic
+ using (OutputCachedPage page = new OutputCachedPage(_cacheSettings))
+ {
+ page.ProcessRequest(HttpContext.Current);
+ }
+ }
+ }
+
+ public override void OnResultExecuted(ResultExecutedContext filterContext)
+ {
+ if (filterContext == null)
+ {
+ throw new ArgumentNullException("filterContext");
+ }
+
+ if (filterContext.IsChildAction)
+ {
+ CompleteChildAction(filterContext, wasException: filterContext.Exception != null);
+ }
+ }
+
+ private static void SetChildActionFilterFinishCallback(ControllerContext controllerContext, Action<bool> callback)
+ {
+ controllerContext.HttpContext.Items[_childActionFilterFinishCallbackKey] = callback;
+ }
+
+ private static IEnumerable<string> SplitVaryByParam(string varyByParam)
+ {
+ if (String.Equals(varyByParam, "none", StringComparison.OrdinalIgnoreCase))
+ {
+ // Vary by nothing
+ return Enumerable.Empty<string>();
+ }
+
+ if (String.Equals(varyByParam, "*", StringComparison.OrdinalIgnoreCase))
+ {
+ // Vary by everything
+ return null;
+ }
+
+ return from part in varyByParam.Split(';') // Vary by specific parameters
+ let trimmed = part.Trim()
+ where !String.IsNullOrEmpty(trimmed)
+ select trimmed;
+ }
+
+ private void ValidateChildActionConfiguration()
+ {
+ if (Duration <= 0)
+ {
+ throw new InvalidOperationException(MvcResources.OutputCacheAttribute_InvalidDuration);
+ }
+
+ if (String.IsNullOrWhiteSpace(VaryByParam))
+ {
+ throw new InvalidOperationException(MvcResources.OutputCacheAttribute_InvalidVaryByParam);
+ }
+
+ if (!String.IsNullOrWhiteSpace(CacheProfile) ||
+ !String.IsNullOrWhiteSpace(SqlDependency) ||
+ !String.IsNullOrWhiteSpace(VaryByContentEncoding) ||
+ !String.IsNullOrWhiteSpace(VaryByHeader) ||
+ _locationWasSet || _noStoreWasSet)
+ {
+ throw new InvalidOperationException(MvcResources.OutputCacheAttribute_ChildAction_UnsupportedSetting);
+ }
+ }
+
+ [SuppressMessage("ASP.NET.Security", "CA5328:ValidateRequestShouldBeEnabled", Justification = "Instances of this type are not created in response to direct user input.")]
+ private sealed class OutputCachedPage : Page
+ {
+ private OutputCacheParameters _cacheSettings;
+
+ public OutputCachedPage(OutputCacheParameters cacheSettings)
+ {
+ // Tracing requires Page IDs to be unique.
+ ID = Guid.NewGuid().ToString();
+ _cacheSettings = cacheSettings;
+ }
+
+ protected override void FrameworkInitialize()
+ {
+ // when you put the <%@ OutputCache %> directive on a page, the generated code calls InitOutputCache() from here
+ base.FrameworkInitialize();
+ InitOutputCache(_cacheSettings);
+ }
+ }
+ }
+}
diff --git a/src/System.Web.Mvc/ParameterBindingInfo.cs b/src/System.Web.Mvc/ParameterBindingInfo.cs
new file mode 100644
index 00000000..a005900c
--- /dev/null
+++ b/src/System.Web.Mvc/ParameterBindingInfo.cs
@@ -0,0 +1,27 @@
+using System.Collections.Generic;
+
+namespace System.Web.Mvc
+{
+ public abstract class ParameterBindingInfo
+ {
+ public virtual IModelBinder Binder
+ {
+ get { return null; }
+ }
+
+ public virtual ICollection<string> Exclude
+ {
+ get { return new string[0]; }
+ }
+
+ public virtual ICollection<string> Include
+ {
+ get { return new string[0]; }
+ }
+
+ public virtual string Prefix
+ {
+ get { return null; }
+ }
+ }
+}
diff --git a/src/System.Web.Mvc/ParameterDescriptor.cs b/src/System.Web.Mvc/ParameterDescriptor.cs
new file mode 100644
index 00000000..fbed437f
--- /dev/null
+++ b/src/System.Web.Mvc/ParameterDescriptor.cs
@@ -0,0 +1,54 @@
+using System.Reflection;
+
+namespace System.Web.Mvc
+{
+ public abstract class ParameterDescriptor : ICustomAttributeProvider
+ {
+ private static readonly EmptyParameterBindingInfo _emptyBindingInfo = new EmptyParameterBindingInfo();
+
+ public abstract ActionDescriptor ActionDescriptor { get; }
+
+ public virtual ParameterBindingInfo BindingInfo
+ {
+ get { return _emptyBindingInfo; }
+ }
+
+ public virtual object DefaultValue
+ {
+ get { return null; }
+ }
+
+ public abstract string ParameterName { get; }
+
+ public abstract Type ParameterType { get; }
+
+ public virtual object[] GetCustomAttributes(bool inherit)
+ {
+ return GetCustomAttributes(typeof(object), inherit);
+ }
+
+ public virtual object[] GetCustomAttributes(Type attributeType, bool inherit)
+ {
+ if (attributeType == null)
+ {
+ throw new ArgumentNullException("attributeType");
+ }
+
+ return (object[])Array.CreateInstance(attributeType, 0);
+ }
+
+ public virtual bool IsDefined(Type attributeType, bool inherit)
+ {
+ if (attributeType == null)
+ {
+ throw new ArgumentNullException("attributeType");
+ }
+
+ return false;
+ }
+
+ private sealed class EmptyParameterBindingInfo : ParameterBindingInfo
+ {
+ }
+ }
+}
diff --git a/src/System.Web.Mvc/ParameterInfoUtil.cs b/src/System.Web.Mvc/ParameterInfoUtil.cs
new file mode 100644
index 00000000..e7343cdf
--- /dev/null
+++ b/src/System.Web.Mvc/ParameterInfoUtil.cs
@@ -0,0 +1,33 @@
+using System.ComponentModel;
+using System.Reflection;
+
+namespace System.Web.Mvc
+{
+ internal static class ParameterInfoUtil
+ {
+ public static bool TryGetDefaultValue(ParameterInfo parameterInfo, out object value)
+ {
+ // this will get the default value as seen by the VB / C# compilers
+ // if no value was baked in, RawDefaultValue returns DBNull.Value
+ object defaultValue = parameterInfo.DefaultValue;
+ if (defaultValue != DBNull.Value)
+ {
+ value = defaultValue;
+ return true;
+ }
+
+ // if the compiler did not bake in a default value, check the [DefaultValue] attribute
+ DefaultValueAttribute[] attrs = (DefaultValueAttribute[])parameterInfo.GetCustomAttributes(typeof(DefaultValueAttribute), false);
+ if (attrs == null || attrs.Length == 0)
+ {
+ value = default(object);
+ return false;
+ }
+ else
+ {
+ value = attrs[0].Value;
+ return true;
+ }
+ }
+ }
+}
diff --git a/src/System.Web.Mvc/PartialViewResult.cs b/src/System.Web.Mvc/PartialViewResult.cs
new file mode 100644
index 00000000..19e08795
--- /dev/null
+++ b/src/System.Web.Mvc/PartialViewResult.cs
@@ -0,0 +1,28 @@
+using System.Globalization;
+using System.Text;
+using System.Web.Mvc.Properties;
+
+namespace System.Web.Mvc
+{
+ public class PartialViewResult : ViewResultBase
+ {
+ protected override ViewEngineResult FindView(ControllerContext context)
+ {
+ ViewEngineResult result = ViewEngineCollection.FindPartialView(context, ViewName);
+ if (result.View != null)
+ {
+ return result;
+ }
+
+ // we need to generate an exception containing all the locations we searched
+ StringBuilder locationsText = new StringBuilder();
+ foreach (string location in result.SearchedLocations)
+ {
+ locationsText.AppendLine();
+ locationsText.Append(location);
+ }
+ throw new InvalidOperationException(String.Format(CultureInfo.CurrentCulture,
+ MvcResources.Common_PartialViewNotFound, ViewName, locationsText));
+ }
+ }
+}
diff --git a/src/System.Web.Mvc/PathHelpers.cs b/src/System.Web.Mvc/PathHelpers.cs
new file mode 100644
index 00000000..b6d25b09
--- /dev/null
+++ b/src/System.Web.Mvc/PathHelpers.cs
@@ -0,0 +1,97 @@
+namespace System.Web.Mvc
+{
+ internal static class PathHelpers
+ {
+ private static UrlRewriterHelper _urlRewriterHelper = new UrlRewriterHelper();
+
+ // this method can accept an app-relative path or an absolute path for contentPath
+ public static string GenerateClientUrl(HttpContextBase httpContext, string contentPath)
+ {
+ if (String.IsNullOrEmpty(contentPath))
+ {
+ return contentPath;
+ }
+
+ // many of the methods we call internally can't handle query strings properly, so just strip it out for
+ // the time being
+ string query;
+ contentPath = StripQuery(contentPath, out query);
+
+ return GenerateClientUrlInternal(httpContext, contentPath) + query;
+ }
+
+ private static string GenerateClientUrlInternal(HttpContextBase httpContext, string contentPath)
+ {
+ if (String.IsNullOrEmpty(contentPath))
+ {
+ return contentPath;
+ }
+
+ // can't call VirtualPathUtility.IsAppRelative since it throws on some inputs
+ bool isAppRelative = contentPath[0] == '~';
+ if (isAppRelative)
+ {
+ string absoluteContentPath = VirtualPathUtility.ToAbsolute(contentPath, httpContext.Request.ApplicationPath);
+ string modifiedAbsoluteContentPath = httpContext.Response.ApplyAppPathModifier(absoluteContentPath);
+ return GenerateClientUrlInternal(httpContext, modifiedAbsoluteContentPath);
+ }
+
+ // we only want to manipulate the path if URL rewriting is active for this request, else we risk breaking the generated URL
+ bool wasRequestRewritten = _urlRewriterHelper.WasRequestRewritten(httpContext);
+ if (!wasRequestRewritten)
+ {
+ return contentPath;
+ }
+
+ // Since the rawUrl represents what the user sees in his browser, it is what we want to use as the base
+ // of our absolute paths. For example, consider mysite.example.com/foo, which is internally
+ // rewritten to content.example.com/mysite/foo. When we want to generate a link to ~/bar, we want to
+ // base it from / instead of /foo, otherwise the user ends up seeing mysite.example.com/foo/bar,
+ // which is incorrect.
+ string relativeUrlToDestination = MakeRelative(httpContext.Request.Path, contentPath);
+ string absoluteUrlToDestination = MakeAbsolute(httpContext.Request.RawUrl, relativeUrlToDestination);
+ return absoluteUrlToDestination;
+ }
+
+ public static string MakeAbsolute(string basePath, string relativePath)
+ {
+ // The Combine() method can't handle query strings on the base path, so we trim it off.
+ string query;
+ basePath = StripQuery(basePath, out query);
+ return VirtualPathUtility.Combine(basePath, relativePath);
+ }
+
+ public static string MakeRelative(string fromPath, string toPath)
+ {
+ string relativeUrl = VirtualPathUtility.MakeRelative(fromPath, toPath);
+ if (String.IsNullOrEmpty(relativeUrl) || relativeUrl[0] == '?')
+ {
+ // Sometimes VirtualPathUtility.MakeRelative() will return an empty string when it meant to return '.',
+ // but links to {empty string} are browser dependent. We replace it with an explicit path to force
+ // consistency across browsers.
+ relativeUrl = "./" + relativeUrl;
+ }
+ return relativeUrl;
+ }
+
+ private static string StripQuery(string path, out string query)
+ {
+ int queryIndex = path.IndexOf('?');
+ if (queryIndex >= 0)
+ {
+ query = path.Substring(queryIndex);
+ return path.Substring(0, queryIndex);
+ }
+ else
+ {
+ query = null;
+ return path;
+ }
+ }
+
+ internal static void ResetUrlRewriterHelper()
+ {
+ _urlRewriterHelper = new UrlRewriterHelper();
+ }
+ }
+}
diff --git a/src/System.Web.Mvc/PreApplicationStartCode.cs b/src/System.Web.Mvc/PreApplicationStartCode.cs
new file mode 100644
index 00000000..05da00ac
--- /dev/null
+++ b/src/System.Web.Mvc/PreApplicationStartCode.cs
@@ -0,0 +1,26 @@
+using System.ComponentModel;
+using System.Web.WebPages.Scope;
+
+namespace System.Web.Mvc
+{
+ [EditorBrowsable(EditorBrowsableState.Never)]
+ public static class PreApplicationStartCode
+ {
+ private static bool _startWasCalled;
+
+ public static void Start()
+ {
+ // Guard against multiple calls. All Start calls are made on same thread, so no lock needed here
+ if (_startWasCalled)
+ {
+ return;
+ }
+ _startWasCalled = true;
+
+ WebPages.Razor.PreApplicationStartCode.Start();
+ WebPages.PreApplicationStartCode.Start();
+
+ ViewContext.GlobalScopeThunk = () => ScopeStorage.CurrentScope;
+ }
+ }
+}
diff --git a/src/System.Web.Mvc/Properties/AssemblyInfo.cs b/src/System.Web.Mvc/Properties/AssemblyInfo.cs
new file mode 100644
index 00000000..939b03c0
--- /dev/null
+++ b/src/System.Web.Mvc/Properties/AssemblyInfo.cs
@@ -0,0 +1,27 @@
+using System.Reflection;
+using System.Runtime.CompilerServices;
+using System.Runtime.InteropServices;
+using System.Security;
+using System.Web;
+using System.Web.Mvc;
+
+[assembly: AssemblyTitle("System.Web.Mvc.dll")]
+[assembly: AssemblyDescription("System.Web.Mvc.dll")]
+[assembly: Guid("4b5f4208-c6b0-4c37-9a41-63325ffa52ad")]
+
+#if !CODE_COVERAGE
+[assembly: AllowPartiallyTrustedCallers]
+#endif
+
+[assembly: InternalsVisibleTo("System.Web.Mvc.Test, PublicKey=0024000004800000940000000602000000240000525341310004000001000100b5fc90e7027f67871e773a8fde8938c81dd402ba65b9201d60593e96c492651e889cc13f1415ebb53fac1131ae0bd333c5ee6021672d9718ea31a8aebd0da0072f25d87dba6fc90ffd598ed4da35e44c398c454307e8e33b8426143daec9f596836f97c8f74750e5975c64e2189f45def46b2a2b1247adc3652bf5c308055da9")]
+[assembly: PreApplicationStartMethod(typeof(PreApplicationStartCode), "Start")]
+[assembly: TypeForwardedTo(typeof(TagBuilder))]
+[assembly: TypeForwardedTo(typeof(TagRenderMode))]
+[assembly: TypeForwardedTo(typeof(HttpAntiForgeryException))]
+[assembly: TypeForwardedTo(typeof(ModelClientValidationEqualToRule))]
+[assembly: TypeForwardedTo(typeof(ModelClientValidationRangeRule))]
+[assembly: TypeForwardedTo(typeof(ModelClientValidationRegexRule))]
+[assembly: TypeForwardedTo(typeof(ModelClientValidationRemoteRule))]
+[assembly: TypeForwardedTo(typeof(ModelClientValidationRequiredRule))]
+[assembly: TypeForwardedTo(typeof(ModelClientValidationRule))]
+[assembly: TypeForwardedTo(typeof(ModelClientValidationStringLengthRule))]
diff --git a/src/System.Web.Mvc/Properties/MvcResources.Designer.cs b/src/System.Web.Mvc/Properties/MvcResources.Designer.cs
new file mode 100644
index 00000000..0618068e
--- /dev/null
+++ b/src/System.Web.Mvc/Properties/MvcResources.Designer.cs
@@ -0,0 +1,1012 @@
+//------------------------------------------------------------------------------
+// <auto-generated>
+// This code was generated by a tool.
+// Runtime Version:4.0.30319.17369
+//
+// Changes to this file may cause incorrect behavior and will be lost if
+// the code is regenerated.
+// </auto-generated>
+//------------------------------------------------------------------------------
+
+namespace System.Web.Mvc.Properties {
+ using System;
+
+
+ /// <summary>
+ /// A strongly-typed resource class, for looking up localized strings, etc.
+ /// </summary>
+ // This class was auto-generated by the StronglyTypedResourceBuilder
+ // class via a tool like ResGen or Visual Studio.
+ // To add or remove a member, edit your .ResX file then rerun ResGen
+ // with the /str option, or rebuild your VS project.
+ [global::System.CodeDom.Compiler.GeneratedCodeAttribute("System.Resources.Tools.StronglyTypedResourceBuilder", "4.0.0.0")]
+ [global::System.Diagnostics.DebuggerNonUserCodeAttribute()]
+ [global::System.Runtime.CompilerServices.CompilerGeneratedAttribute()]
+ internal class MvcResources {
+
+ private static global::System.Resources.ResourceManager resourceMan;
+
+ private static global::System.Globalization.CultureInfo resourceCulture;
+
+ [global::System.Diagnostics.CodeAnalysis.SuppressMessageAttribute("Microsoft.Performance", "CA1811:AvoidUncalledPrivateCode")]
+ internal MvcResources() {
+ }
+
+ /// <summary>
+ /// Returns the cached ResourceManager instance used by this class.
+ /// </summary>
+ [global::System.ComponentModel.EditorBrowsableAttribute(global::System.ComponentModel.EditorBrowsableState.Advanced)]
+ internal static global::System.Resources.ResourceManager ResourceManager {
+ get {
+ if (object.ReferenceEquals(resourceMan, null)) {
+ global::System.Resources.ResourceManager temp = new global::System.Resources.ResourceManager("System.Web.Mvc.Properties.MvcResources", typeof(MvcResources).Assembly);
+ resourceMan = temp;
+ }
+ return resourceMan;
+ }
+ }
+
+ /// <summary>
+ /// Overrides the current thread's CurrentUICulture property for all
+ /// resource lookups using this strongly typed resource class.
+ /// </summary>
+ [global::System.ComponentModel.EditorBrowsableAttribute(global::System.ComponentModel.EditorBrowsableState.Advanced)]
+ internal static global::System.Globalization.CultureInfo Culture {
+ get {
+ return resourceCulture;
+ }
+ set {
+ resourceCulture = value;
+ }
+ }
+
+ /// <summary>
+ /// Looks up a localized string similar to The current request for action &apos;{0}&apos; on controller type &apos;{1}&apos; is ambiguous between the following action methods:{2}.
+ /// </summary>
+ internal static string ActionMethodSelector_AmbiguousMatch {
+ get {
+ return ResourceManager.GetString("ActionMethodSelector_AmbiguousMatch", resourceCulture);
+ }
+ }
+
+ /// <summary>
+ /// Looks up a localized string similar to {0} on type {1}.
+ /// </summary>
+ internal static string ActionMethodSelector_AmbiguousMatchType {
+ get {
+ return ResourceManager.GetString("ActionMethodSelector_AmbiguousMatchType", resourceCulture);
+ }
+ }
+
+ /// <summary>
+ /// Looks up a localized string similar to The asynchronous action method &apos;{0}&apos; cannot be executed synchronously..
+ /// </summary>
+ internal static string AsyncActionDescriptor_CannotExecuteSynchronously {
+ get {
+ return ResourceManager.GetString("AsyncActionDescriptor_CannotExecuteSynchronously", resourceCulture);
+ }
+ }
+
+ /// <summary>
+ /// Looks up a localized string similar to Lookup for method &apos;{0}&apos; on controller type &apos;{1}&apos; failed because of an ambiguity between the following methods:{2}.
+ /// </summary>
+ internal static string AsyncActionMethodSelector_AmbiguousMethodMatch {
+ get {
+ return ResourceManager.GetString("AsyncActionMethodSelector_AmbiguousMethodMatch", resourceCulture);
+ }
+ }
+
+ /// <summary>
+ /// Looks up a localized string similar to Could not locate a method named &apos;{0}&apos; on controller type {1}..
+ /// </summary>
+ internal static string AsyncActionMethodSelector_CouldNotFindMethod {
+ get {
+ return ResourceManager.GetString("AsyncActionMethodSelector_CouldNotFindMethod", resourceCulture);
+ }
+ }
+
+ /// <summary>
+ /// Looks up a localized string similar to The provided IAsyncResult has already been consumed..
+ /// </summary>
+ internal static string AsyncCommon_AsyncResultAlreadyConsumed {
+ get {
+ return ResourceManager.GetString("AsyncCommon_AsyncResultAlreadyConsumed", resourceCulture);
+ }
+ }
+
+ /// <summary>
+ /// Looks up a localized string similar to The controller of type &apos;{0}&apos; must subclass AsyncController or implement the IAsyncManagerContainer interface..
+ /// </summary>
+ internal static string AsyncCommon_ControllerMustImplementIAsyncManagerContainer {
+ get {
+ return ResourceManager.GetString("AsyncCommon_ControllerMustImplementIAsyncManagerContainer", resourceCulture);
+ }
+ }
+
+ /// <summary>
+ /// Looks up a localized string similar to The provided IAsyncResult is not valid for this method..
+ /// </summary>
+ internal static string AsyncCommon_InvalidAsyncResult {
+ get {
+ return ResourceManager.GetString("AsyncCommon_InvalidAsyncResult", resourceCulture);
+ }
+ }
+
+ /// <summary>
+ /// Looks up a localized string similar to The timeout value must be non-negative or Timeout.Infinite..
+ /// </summary>
+ internal static string AsyncCommon_InvalidTimeout {
+ get {
+ return ResourceManager.GetString("AsyncCommon_InvalidTimeout", resourceCulture);
+ }
+ }
+
+ /// <summary>
+ /// Looks up a localized string similar to AuthorizeAttribute cannot be used within a child action caching block..
+ /// </summary>
+ internal static string AuthorizeAttribute_CannotUseWithinChildActionCache {
+ get {
+ return ResourceManager.GetString("AuthorizeAttribute_CannotUseWithinChildActionCache", resourceCulture);
+ }
+ }
+
+ /// <summary>
+ /// Looks up a localized string similar to The action &apos;{0}&apos; is accessible only by a child request..
+ /// </summary>
+ internal static string ChildActionOnlyAttribute_MustBeInChildRequest {
+ get {
+ return ResourceManager.GetString("ChildActionOnlyAttribute_MustBeInChildRequest", resourceCulture);
+ }
+ }
+
+ /// <summary>
+ /// Looks up a localized string similar to The field {0} must be a date..
+ /// </summary>
+ internal static string ClientDataTypeModelValidatorProvider_FieldMustBeDate {
+ get {
+ return ResourceManager.GetString("ClientDataTypeModelValidatorProvider_FieldMustBeDate", resourceCulture);
+ }
+ }
+
+ /// <summary>
+ /// Looks up a localized string similar to The field {0} must be a number..
+ /// </summary>
+ internal static string ClientDataTypeModelValidatorProvider_FieldMustBeNumeric {
+ get {
+ return ResourceManager.GetString("ClientDataTypeModelValidatorProvider_FieldMustBeNumeric", resourceCulture);
+ }
+ }
+
+ /// <summary>
+ /// Looks up a localized string similar to No route in the route table matches the supplied values..
+ /// </summary>
+ internal static string Common_NoRouteMatched {
+ get {
+ return ResourceManager.GetString("Common_NoRouteMatched", resourceCulture);
+ }
+ }
+
+ /// <summary>
+ /// Looks up a localized string similar to Value cannot be null or empty..
+ /// </summary>
+ internal static string Common_NullOrEmpty {
+ get {
+ return ResourceManager.GetString("Common_NullOrEmpty", resourceCulture);
+ }
+ }
+
+ /// <summary>
+ /// Looks up a localized string similar to The partial view &apos;{0}&apos; was not found or no view engine supports the searched locations. The following locations were searched:{1}.
+ /// </summary>
+ internal static string Common_PartialViewNotFound {
+ get {
+ return ResourceManager.GetString("Common_PartialViewNotFound", resourceCulture);
+ }
+ }
+
+ /// <summary>
+ /// Looks up a localized string similar to The property &apos;{0}&apos; cannot be null or empty..
+ /// </summary>
+ internal static string Common_PropertyCannotBeNullOrEmpty {
+ get {
+ return ResourceManager.GetString("Common_PropertyCannotBeNullOrEmpty", resourceCulture);
+ }
+ }
+
+ /// <summary>
+ /// Looks up a localized string similar to The property {0}.{1} could not be found..
+ /// </summary>
+ internal static string Common_PropertyNotFound {
+ get {
+ return ResourceManager.GetString("Common_PropertyNotFound", resourceCulture);
+ }
+ }
+
+ /// <summary>
+ /// Looks up a localized string similar to False.
+ /// </summary>
+ internal static string Common_TriState_False {
+ get {
+ return ResourceManager.GetString("Common_TriState_False", resourceCulture);
+ }
+ }
+
+ /// <summary>
+ /// Looks up a localized string similar to Not Set.
+ /// </summary>
+ internal static string Common_TriState_NotSet {
+ get {
+ return ResourceManager.GetString("Common_TriState_NotSet", resourceCulture);
+ }
+ }
+
+ /// <summary>
+ /// Looks up a localized string similar to True.
+ /// </summary>
+ internal static string Common_TriState_True {
+ get {
+ return ResourceManager.GetString("Common_TriState_True", resourceCulture);
+ }
+ }
+
+ /// <summary>
+ /// Looks up a localized string similar to The type {0} must derive from {1}.
+ /// </summary>
+ internal static string Common_TypeMustDriveFromType {
+ get {
+ return ResourceManager.GetString("Common_TypeMustDriveFromType", resourceCulture);
+ }
+ }
+
+ /// <summary>
+ /// Looks up a localized string similar to The value &apos;{0}&apos; is invalid..
+ /// </summary>
+ internal static string Common_ValueNotValidForProperty {
+ get {
+ return ResourceManager.GetString("Common_ValueNotValidForProperty", resourceCulture);
+ }
+ }
+
+ /// <summary>
+ /// Looks up a localized string similar to The view &apos;{0}&apos; or its master was not found or no view engine supports the searched locations. The following locations were searched:{1}.
+ /// </summary>
+ internal static string Common_ViewNotFound {
+ get {
+ return ResourceManager.GetString("Common_ViewNotFound", resourceCulture);
+ }
+ }
+
+ /// <summary>
+ /// Looks up a localized string similar to &apos;{0}&apos; and &apos;{1}&apos; do not match..
+ /// </summary>
+ internal static string CompareAttribute_MustMatch {
+ get {
+ return ResourceManager.GetString("CompareAttribute_MustMatch", resourceCulture);
+ }
+ }
+
+ /// <summary>
+ /// Looks up a localized string similar to Could not find a property named {0}..
+ /// </summary>
+ internal static string CompareAttribute_UnknownProperty {
+ get {
+ return ResourceManager.GetString("CompareAttribute_UnknownProperty", resourceCulture);
+ }
+ }
+
+ /// <summary>
+ /// Looks up a localized string similar to A public action method &apos;{0}&apos; was not found on controller &apos;{1}&apos;..
+ /// </summary>
+ internal static string Controller_UnknownAction {
+ get {
+ return ResourceManager.GetString("Controller_UnknownAction", resourceCulture);
+ }
+ }
+
+ /// <summary>
+ /// Looks up a localized string similar to The model of type &apos;{0}&apos; could not be updated..
+ /// </summary>
+ internal static string Controller_UpdateModel_UpdateUnsuccessful {
+ get {
+ return ResourceManager.GetString("Controller_UpdateModel_UpdateUnsuccessful", resourceCulture);
+ }
+ }
+
+ /// <summary>
+ /// Looks up a localized string similar to The model of type &apos;{0}&apos; is not valid..
+ /// </summary>
+ internal static string Controller_Validate_ValidationFailed {
+ get {
+ return ResourceManager.GetString("Controller_Validate_ValidationFailed", resourceCulture);
+ }
+ }
+
+ /// <summary>
+ /// Looks up a localized string similar to Cannot execute Controller with a null HttpContext..
+ /// </summary>
+ internal static string ControllerBase_CannotExecuteWithNullHttpContext {
+ get {
+ return ResourceManager.GetString("ControllerBase_CannotExecuteWithNullHttpContext", resourceCulture);
+ }
+ }
+
+ /// <summary>
+ /// Looks up a localized string similar to A single instance of controller &apos;{0}&apos; cannot be used to handle multiple requests. If a custom controller factory is in use, make sure that it creates a new instance of the controller for each request..
+ /// </summary>
+ internal static string ControllerBase_CannotHandleMultipleRequests {
+ get {
+ return ResourceManager.GetString("ControllerBase_CannotHandleMultipleRequests", resourceCulture);
+ }
+ }
+
+ /// <summary>
+ /// Looks up a localized string similar to An error occurred when trying to create the IControllerFactory &apos;{0}&apos;. Make sure that the controller factory has a public parameterless constructor..
+ /// </summary>
+ internal static string ControllerBuilder_ErrorCreatingControllerFactory {
+ get {
+ return ResourceManager.GetString("ControllerBuilder_ErrorCreatingControllerFactory", resourceCulture);
+ }
+ }
+
+ /// <summary>
+ /// Looks up a localized string similar to The IControllerFactory &apos;{0}&apos; did not return a controller for the name &apos;{1}&apos;..
+ /// </summary>
+ internal static string ControllerBuilder_FactoryReturnedNull {
+ get {
+ return ResourceManager.GetString("ControllerBuilder_FactoryReturnedNull", resourceCulture);
+ }
+ }
+
+ /// <summary>
+ /// Looks up a localized string similar to The controller factory type &apos;{0}&apos; must implement the IControllerFactory interface..
+ /// </summary>
+ internal static string ControllerBuilder_MissingIControllerFactory {
+ get {
+ return ResourceManager.GetString("ControllerBuilder_MissingIControllerFactory", resourceCulture);
+ }
+ }
+
+ /// <summary>
+ /// Looks up a localized string similar to The view found at &apos;{0}&apos; was not created..
+ /// </summary>
+ internal static string CshtmlView_ViewCouldNotBeCreated {
+ get {
+ return ResourceManager.GetString("CshtmlView_ViewCouldNotBeCreated", resourceCulture);
+ }
+ }
+
+ /// <summary>
+ /// Looks up a localized string similar to The view at &apos;{0}&apos; must derive from WebViewPage, or WebViewPage&lt;TModel&gt;..
+ /// </summary>
+ internal static string CshtmlView_WrongViewBase {
+ get {
+ return ResourceManager.GetString("CshtmlView_WrongViewBase", resourceCulture);
+ }
+ }
+
+ /// <summary>
+ /// Looks up a localized string similar to {0} has a DisplayColumn attribute for {1}, but property {1} does not exist..
+ /// </summary>
+ internal static string DataAnnotationsModelMetadataProvider_UnknownProperty {
+ get {
+ return ResourceManager.GetString("DataAnnotationsModelMetadataProvider_UnknownProperty", resourceCulture);
+ }
+ }
+
+ /// <summary>
+ /// Looks up a localized string similar to {0} has a DisplayColumn attribute for {1}, but property {1} does not have a public getter..
+ /// </summary>
+ internal static string DataAnnotationsModelMetadataProvider_UnreadableProperty {
+ get {
+ return ResourceManager.GetString("DataAnnotationsModelMetadataProvider_UnreadableProperty", resourceCulture);
+ }
+ }
+
+ /// <summary>
+ /// Looks up a localized string similar to The type {0} must have a public constructor which accepts three parameters of types {1}, {2}, and {3}.
+ /// </summary>
+ internal static string DataAnnotationsModelValidatorProvider_ConstructorRequirements {
+ get {
+ return ResourceManager.GetString("DataAnnotationsModelValidatorProvider_ConstructorRequirements", resourceCulture);
+ }
+ }
+
+ /// <summary>
+ /// Looks up a localized string similar to The type {0} must have a public constructor which accepts two parameters of types {1} and {2}..
+ /// </summary>
+ internal static string DataAnnotationsModelValidatorProvider_ValidatableConstructorRequirements {
+ get {
+ return ResourceManager.GetString("DataAnnotationsModelValidatorProvider_ValidatableConstructorRequirements", resourceCulture);
+ }
+ }
+
+ /// <summary>
+ /// Looks up a localized string similar to Multiple types were found that match the controller named &apos;{0}&apos;. This can happen if the route that services this request does not specify namespaces to search for a controller that matches the request. If this is the case, register this route by calling an overload of the &apos;MapRoute&apos; method that takes a &apos;namespaces&apos; parameter.
+ ///
+ ///The request for &apos;{0}&apos; has found the following matching controllers:{1}.
+ /// </summary>
+ internal static string DefaultControllerFactory_ControllerNameAmbiguous_WithoutRouteUrl {
+ get {
+ return ResourceManager.GetString("DefaultControllerFactory_ControllerNameAmbiguous_WithoutRouteUrl", resourceCulture);
+ }
+ }
+
+ /// <summary>
+ /// Looks up a localized string similar to Multiple types were found that match the controller named &apos;{0}&apos;. This can happen if the route that services this request (&apos;{1}&apos;) does not specify namespaces to search for a controller that matches the request. If this is the case, register this route by calling an overload of the &apos;MapRoute&apos; method that takes a &apos;namespaces&apos; parameter.
+ ///
+ ///The request for &apos;{0}&apos; has found the following matching controllers:{2}.
+ /// </summary>
+ internal static string DefaultControllerFactory_ControllerNameAmbiguous_WithRouteUrl {
+ get {
+ return ResourceManager.GetString("DefaultControllerFactory_ControllerNameAmbiguous_WithRouteUrl", resourceCulture);
+ }
+ }
+
+ /// <summary>
+ /// Looks up a localized string similar to An error occurred when trying to create a controller of type &apos;{0}&apos;. Make sure that the controller has a parameterless public constructor..
+ /// </summary>
+ internal static string DefaultControllerFactory_ErrorCreatingController {
+ get {
+ return ResourceManager.GetString("DefaultControllerFactory_ErrorCreatingController", resourceCulture);
+ }
+ }
+
+ /// <summary>
+ /// Looks up a localized string similar to The controller for path &apos;{0}&apos; was not found or does not implement IController..
+ /// </summary>
+ internal static string DefaultControllerFactory_NoControllerFound {
+ get {
+ return ResourceManager.GetString("DefaultControllerFactory_NoControllerFound", resourceCulture);
+ }
+ }
+
+ /// <summary>
+ /// Looks up a localized string similar to The controller type &apos;{0}&apos; must implement IController..
+ /// </summary>
+ internal static string DefaultControllerFactory_TypeDoesNotSubclassControllerBase {
+ get {
+ return ResourceManager.GetString("DefaultControllerFactory_TypeDoesNotSubclassControllerBase", resourceCulture);
+ }
+ }
+
+ /// <summary>
+ /// Looks up a localized string similar to The value &apos;{0}&apos; is not valid for {1}..
+ /// </summary>
+ internal static string DefaultModelBinder_ValueInvalid {
+ get {
+ return ResourceManager.GetString("DefaultModelBinder_ValueInvalid", resourceCulture);
+ }
+ }
+
+ /// <summary>
+ /// Looks up a localized string similar to A value is required..
+ /// </summary>
+ internal static string DefaultModelBinder_ValueRequired {
+ get {
+ return ResourceManager.GetString("DefaultModelBinder_ValueRequired", resourceCulture);
+ }
+ }
+
+ /// <summary>
+ /// Looks up a localized string similar to The number of ticks for the TimeSpan value must be greater than or equal to 0..
+ /// </summary>
+ internal static string DefaultViewLocationCache_NegativeTimeSpan {
+ get {
+ return ResourceManager.GetString("DefaultViewLocationCache_NegativeTimeSpan", resourceCulture);
+ }
+ }
+
+ /// <summary>
+ /// Looks up a localized string similar to The type {0} does not appear to implement Microsoft.Practices.ServiceLocation.IServiceLocator..
+ /// </summary>
+ internal static string DependencyResolver_DoesNotImplementICommonServiceLocator {
+ get {
+ return ResourceManager.GetString("DependencyResolver_DoesNotImplementICommonServiceLocator", resourceCulture);
+ }
+ }
+
+ /// <summary>
+ /// Looks up a localized string similar to The type &apos;{0}&apos; does not inherit from Exception..
+ /// </summary>
+ internal static string ExceptionViewAttribute_NonExceptionType {
+ get {
+ return ResourceManager.GetString("ExceptionViewAttribute_NonExceptionType", resourceCulture);
+ }
+ }
+
+ /// <summary>
+ /// Looks up a localized string similar to The expression compiler was unable to evaluate the indexer expression &apos;{0}&apos; because it references the model parameter &apos;{1}&apos; which is unavailable..
+ /// </summary>
+ internal static string ExpressionHelper_InvalidIndexerExpression {
+ get {
+ return ResourceManager.GetString("ExpressionHelper_InvalidIndexerExpression", resourceCulture);
+ }
+ }
+
+ /// <summary>
+ /// Looks up a localized string similar to Order must be greater than or equal to -1..
+ /// </summary>
+ internal static string FilterAttribute_OrderOutOfRange {
+ get {
+ return ResourceManager.GetString("FilterAttribute_OrderOutOfRange", resourceCulture);
+ }
+ }
+
+ /// <summary>
+ /// Looks up a localized string similar to The GET and POST HTTP methods are not supported..
+ /// </summary>
+ internal static string HtmlHelper_InvalidHttpMethod {
+ get {
+ return ResourceManager.GetString("HtmlHelper_InvalidHttpMethod", resourceCulture);
+ }
+ }
+
+ /// <summary>
+ /// Looks up a localized string similar to The specified HttpVerbs value is not supported. The supported values are Delete, Head, and Put..
+ /// </summary>
+ internal static string HtmlHelper_InvalidHttpVerb {
+ get {
+ return ResourceManager.GetString("HtmlHelper_InvalidHttpVerb", resourceCulture);
+ }
+ }
+
+ /// <summary>
+ /// Looks up a localized string similar to There is no ViewData item of type &apos;{1}&apos; that has the key &apos;{0}&apos;..
+ /// </summary>
+ internal static string HtmlHelper_MissingSelectData {
+ get {
+ return ResourceManager.GetString("HtmlHelper_MissingSelectData", resourceCulture);
+ }
+ }
+
+ /// <summary>
+ /// Looks up a localized string similar to The parameter &apos;{0}&apos; must evaluate to an IEnumerable when multiple selection is allowed..
+ /// </summary>
+ internal static string HtmlHelper_SelectExpressionNotEnumerable {
+ get {
+ return ResourceManager.GetString("HtmlHelper_SelectExpressionNotEnumerable", resourceCulture);
+ }
+ }
+
+ /// <summary>
+ /// Looks up a localized string similar to The value must be greater than or equal to zero..
+ /// </summary>
+ internal static string HtmlHelper_TextAreaParameterOutOfRange {
+ get {
+ return ResourceManager.GetString("HtmlHelper_TextAreaParameterOutOfRange", resourceCulture);
+ }
+ }
+
+ /// <summary>
+ /// Looks up a localized string similar to The ViewData item that has the key &apos;{0}&apos; is of type &apos;{1}&apos; but must be of type &apos;{2}&apos;..
+ /// </summary>
+ internal static string HtmlHelper_WrongSelectDataType {
+ get {
+ return ResourceManager.GetString("HtmlHelper_WrongSelectDataType", resourceCulture);
+ }
+ }
+
+ /// <summary>
+ /// Looks up a localized string similar to This request has been blocked because sensitive information could be disclosed to third party web sites when this is used in a GET request. To allow GET requests, set JsonRequestBehavior to AllowGet..
+ /// </summary>
+ internal static string JsonRequest_GetNotAllowed {
+ get {
+ return ResourceManager.GetString("JsonRequest_GetNotAllowed", resourceCulture);
+ }
+ }
+
+ /// <summary>
+ /// Looks up a localized string similar to The JSON request was too large to be deserialized..
+ /// </summary>
+ internal static string JsonValueProviderFactory_RequestTooLarge {
+ get {
+ return ResourceManager.GetString("JsonValueProviderFactory_RequestTooLarge", resourceCulture);
+ }
+ }
+
+ /// <summary>
+ /// Looks up a localized string similar to An error occurred when trying to create the IModelBinder &apos;{0}&apos;. Make sure that the binder has a public parameterless constructor..
+ /// </summary>
+ internal static string ModelBinderAttribute_ErrorCreatingModelBinder {
+ get {
+ return ResourceManager.GetString("ModelBinderAttribute_ErrorCreatingModelBinder", resourceCulture);
+ }
+ }
+
+ /// <summary>
+ /// Looks up a localized string similar to The type &apos;{0}&apos; does not implement the IModelBinder interface..
+ /// </summary>
+ internal static string ModelBinderAttribute_TypeNotIModelBinder {
+ get {
+ return ResourceManager.GetString("ModelBinderAttribute_TypeNotIModelBinder", resourceCulture);
+ }
+ }
+
+ /// <summary>
+ /// Looks up a localized string similar to The type &apos;{0}&apos; contains multiple attributes that inherit from CustomModelBinderAttribute..
+ /// </summary>
+ internal static string ModelBinderDictionary_MultipleAttributes {
+ get {
+ return ResourceManager.GetString("ModelBinderDictionary_MultipleAttributes", resourceCulture);
+ }
+ }
+
+ /// <summary>
+ /// Looks up a localized string similar to This property setter is obsolete, because its value is derived from ModelMetadata.Model now..
+ /// </summary>
+ internal static string ModelMetadata_PropertyNotSettable {
+ get {
+ return ResourceManager.GetString("ModelMetadata_PropertyNotSettable", resourceCulture);
+ }
+ }
+
+ /// <summary>
+ /// Looks up a localized string similar to This constructor is obsolete, because its functionality has been moved to MvcForm(ViewContext) now..
+ /// </summary>
+ internal static string MvcForm_ConstructorObsolete {
+ get {
+ return ResourceManager.GetString("MvcForm_ConstructorObsolete", resourceCulture);
+ }
+ }
+
+ /// <summary>
+ /// Looks up a localized string similar to The &apos;inherits&apos; keyword is not allowed when a &apos;{0}&apos; keyword is used..
+ /// </summary>
+ internal static string MvcRazorCodeParser_CannotHaveModelAndInheritsKeyword {
+ get {
+ return ResourceManager.GetString("MvcRazorCodeParser_CannotHaveModelAndInheritsKeyword", resourceCulture);
+ }
+ }
+
+ /// <summary>
+ /// Looks up a localized string similar to The &apos;{0}&apos; keyword must be followed by a type name on the same line..
+ /// </summary>
+ internal static string MvcRazorCodeParser_ModelKeywordMustBeFollowedByTypeName {
+ get {
+ return ResourceManager.GetString("MvcRazorCodeParser_ModelKeywordMustBeFollowedByTypeName", resourceCulture);
+ }
+ }
+
+ /// <summary>
+ /// Looks up a localized string similar to Only one &apos;{0}&apos; statement is allowed in a file..
+ /// </summary>
+ internal static string MvcRazorCodeParser_OnlyOneModelStatementIsAllowed {
+ get {
+ return ResourceManager.GetString("MvcRazorCodeParser_OnlyOneModelStatementIsAllowed", resourceCulture);
+ }
+ }
+
+ /// <summary>
+ /// Looks up a localized string similar to The matched route does not include a &apos;controller&apos; route value, which is required..
+ /// </summary>
+ internal static string MvcRouteHandler_RouteValuesHasNoController {
+ get {
+ return ResourceManager.GetString("MvcRouteHandler_RouteValuesHasNoController", resourceCulture);
+ }
+ }
+
+ /// <summary>
+ /// Looks up a localized string similar to OutputCacheAttribute is not allowed on child actions which are children of an already cached child action..
+ /// </summary>
+ internal static string OutputCacheAttribute_CannotNestChildCache {
+ get {
+ return ResourceManager.GetString("OutputCacheAttribute_CannotNestChildCache", resourceCulture);
+ }
+ }
+
+ /// <summary>
+ /// Looks up a localized string similar to OutputCacheAttribute for child actions only supports Duration, VaryByCustom, and VaryByParam values. Please do not set CacheProfile, Location, NoStore, SqlDependency, VaryByContentEncoding, or VaryByHeader values for child actions..
+ /// </summary>
+ internal static string OutputCacheAttribute_ChildAction_UnsupportedSetting {
+ get {
+ return ResourceManager.GetString("OutputCacheAttribute_ChildAction_UnsupportedSetting", resourceCulture);
+ }
+ }
+
+ /// <summary>
+ /// Looks up a localized string similar to Duration must be a positive number..
+ /// </summary>
+ internal static string OutputCacheAttribute_InvalidDuration {
+ get {
+ return ResourceManager.GetString("OutputCacheAttribute_InvalidDuration", resourceCulture);
+ }
+ }
+
+ /// <summary>
+ /// Looks up a localized string similar to VaryByParam must be &apos;*&apos;, &apos;none&apos;, or a semicolon-delimited list of keys..
+ /// </summary>
+ internal static string OutputCacheAttribute_InvalidVaryByParam {
+ get {
+ return ResourceManager.GetString("OutputCacheAttribute_InvalidVaryByParam", resourceCulture);
+ }
+ }
+
+ /// <summary>
+ /// Looks up a localized string similar to Child actions are not allowed to perform redirect actions..
+ /// </summary>
+ internal static string RedirectAction_CannotRedirectInChildAction {
+ get {
+ return ResourceManager.GetString("RedirectAction_CannotRedirectInChildAction", resourceCulture);
+ }
+ }
+
+ /// <summary>
+ /// Looks up a localized string similar to Cannot create a descriptor for instance method &apos;{0}&apos; on type &apos;{1}&apos; because the type does not derive from ControllerBase..
+ /// </summary>
+ internal static string ReflectedActionDescriptor_CannotCallInstanceMethodOnNonControllerType {
+ get {
+ return ResourceManager.GetString("ReflectedActionDescriptor_CannotCallInstanceMethodOnNonControllerType", resourceCulture);
+ }
+ }
+
+ /// <summary>
+ /// Looks up a localized string similar to Cannot call action method &apos;{0}&apos; on controller &apos;{1}&apos; because the parameter &apos;{2}&apos; is passed by reference..
+ /// </summary>
+ internal static string ReflectedActionDescriptor_CannotCallMethodsWithOutOrRefParameters {
+ get {
+ return ResourceManager.GetString("ReflectedActionDescriptor_CannotCallMethodsWithOutOrRefParameters", resourceCulture);
+ }
+ }
+
+ /// <summary>
+ /// Looks up a localized string similar to Cannot call action method &apos;{0}&apos; on controller &apos;{1}&apos; because the action method is a generic method..
+ /// </summary>
+ internal static string ReflectedActionDescriptor_CannotCallOpenGenericMethods {
+ get {
+ return ResourceManager.GetString("ReflectedActionDescriptor_CannotCallOpenGenericMethods", resourceCulture);
+ }
+ }
+
+ /// <summary>
+ /// Looks up a localized string similar to Cannot call action method &apos;{0}&apos; on controller &apos;{1}&apos; because the action method is a static method..
+ /// </summary>
+ internal static string ReflectedActionDescriptor_CannotCallStaticMethod {
+ get {
+ return ResourceManager.GetString("ReflectedActionDescriptor_CannotCallStaticMethod", resourceCulture);
+ }
+ }
+
+ /// <summary>
+ /// Looks up a localized string similar to The parameters dictionary contains a null entry for parameter &apos;{0}&apos; of non-nullable type &apos;{1}&apos; for method &apos;{2}&apos; in &apos;{3}&apos;. An optional parameter must be a reference type, a nullable type, or be declared as an optional parameter..
+ /// </summary>
+ internal static string ReflectedActionDescriptor_ParameterCannotBeNull {
+ get {
+ return ResourceManager.GetString("ReflectedActionDescriptor_ParameterCannotBeNull", resourceCulture);
+ }
+ }
+
+ /// <summary>
+ /// Looks up a localized string similar to The parameters dictionary does not contain an entry for parameter &apos;{0}&apos; of type &apos;{1}&apos; for method &apos;{2}&apos; in &apos;{3}&apos;. The dictionary must contain an entry for each parameter, including parameters that have null values..
+ /// </summary>
+ internal static string ReflectedActionDescriptor_ParameterNotInDictionary {
+ get {
+ return ResourceManager.GetString("ReflectedActionDescriptor_ParameterNotInDictionary", resourceCulture);
+ }
+ }
+
+ /// <summary>
+ /// Looks up a localized string similar to The parameters dictionary contains an invalid entry for parameter &apos;{0}&apos; for method &apos;{1}&apos; in &apos;{2}&apos;. The dictionary contains a value of type &apos;{3}&apos;, but the parameter requires a value of type &apos;{4}&apos;..
+ /// </summary>
+ internal static string ReflectedActionDescriptor_ParameterValueHasWrongType {
+ get {
+ return ResourceManager.GetString("ReflectedActionDescriptor_ParameterValueHasWrongType", resourceCulture);
+ }
+ }
+
+ /// <summary>
+ /// Looks up a localized string similar to The parameter &apos;{0}&apos; on method &apos;{1}&apos; contains multiple attributes that inherit from CustomModelBinderAttribute..
+ /// </summary>
+ internal static string ReflectedParameterBindingInfo_MultipleConverterAttributes {
+ get {
+ return ResourceManager.GetString("ReflectedParameterBindingInfo_MultipleConverterAttributes", resourceCulture);
+ }
+ }
+
+ /// <summary>
+ /// Looks up a localized string similar to No url for remote validation could be found..
+ /// </summary>
+ internal static string RemoteAttribute_NoUrlFound {
+ get {
+ return ResourceManager.GetString("RemoteAttribute_NoUrlFound", resourceCulture);
+ }
+ }
+
+ /// <summary>
+ /// Looks up a localized string similar to &apos;{0}&apos; is invalid..
+ /// </summary>
+ internal static string RemoteAttribute_RemoteValidationFailed {
+ get {
+ return ResourceManager.GetString("RemoteAttribute_RemoteValidationFailed", resourceCulture);
+ }
+ }
+
+ /// <summary>
+ /// Looks up a localized string similar to The requested resource can only be accessed via SSL..
+ /// </summary>
+ internal static string RequireHttpsAttribute_MustUseSsl {
+ get {
+ return ResourceManager.GetString("RequireHttpsAttribute_MustUseSsl", resourceCulture);
+ }
+ }
+
+ /// <summary>
+ /// Looks up a localized string similar to The SessionStateTempDataProvider class requires session state to be enabled..
+ /// </summary>
+ internal static string SessionStateTempDataProvider_SessionStateDisabled {
+ get {
+ return ResourceManager.GetString("SessionStateTempDataProvider_SessionStateDisabled", resourceCulture);
+ }
+ }
+
+ /// <summary>
+ /// Looks up a localized string similar to An instance of {0} was found in the resolver as well as a custom registered provider in {1}. Please set only one or the other..
+ /// </summary>
+ internal static string SingleServiceResolver_CannotRegisterTwoInstances {
+ get {
+ return ResourceManager.GetString("SingleServiceResolver_CannotRegisterTwoInstances", resourceCulture);
+ }
+ }
+
+ /// <summary>
+ /// Looks up a localized string similar to An operation that crossed a synchronization context failed. See the inner exception for more information..
+ /// </summary>
+ internal static string SynchronizationContextUtil_ExceptionThrown {
+ get {
+ return ResourceManager.GetString("SynchronizationContextUtil_ExceptionThrown", resourceCulture);
+ }
+ }
+
+ /// <summary>
+ /// Looks up a localized string similar to The asynchronous action method &apos;{0}&apos; returns a Task, which cannot be executed synchronously..
+ /// </summary>
+ internal static string TaskAsyncActionDescriptor_CannotExecuteSynchronously {
+ get {
+ return ResourceManager.GetString("TaskAsyncActionDescriptor_CannotExecuteSynchronously", resourceCulture);
+ }
+ }
+
+ /// <summary>
+ /// Looks up a localized string similar to Unable to locate an appropriate template for type {0}..
+ /// </summary>
+ internal static string TemplateHelpers_NoTemplate {
+ get {
+ return ResourceManager.GetString("TemplateHelpers_NoTemplate", resourceCulture);
+ }
+ }
+
+ /// <summary>
+ /// Looks up a localized string similar to Templates can be used only with field access, property access, single-dimension array index, or single-parameter custom indexer expressions..
+ /// </summary>
+ internal static string TemplateHelpers_TemplateLimitations {
+ get {
+ return ResourceManager.GetString("TemplateHelpers_TemplateLimitations", resourceCulture);
+ }
+ }
+
+ /// <summary>
+ /// Looks up a localized string similar to The Collection template was used with an object of type &apos;{0}&apos;, which does not implement System.IEnumerable..
+ /// </summary>
+ internal static string Templates_TypeMustImplementIEnumerable {
+ get {
+ return ResourceManager.GetString("Templates_TypeMustImplementIEnumerable", resourceCulture);
+ }
+ }
+
+ /// <summary>
+ /// Looks up a localized string similar to This file is automatically generated. Please do not modify the contents of this file..
+ /// </summary>
+ internal static string TypeCache_DoNotModify {
+ get {
+ return ResourceManager.GetString("TypeCache_DoNotModify", resourceCulture);
+ }
+ }
+
+ /// <summary>
+ /// Looks up a localized string similar to The model object inside the metadata claimed to be compatible with {0}, but was actually {1}..
+ /// </summary>
+ internal static string ValidatableObjectAdapter_IncompatibleType {
+ get {
+ return ResourceManager.GetString("ValidatableObjectAdapter_IncompatibleType", resourceCulture);
+ }
+ }
+
+ /// <summary>
+ /// Looks up a localized string similar to The parameter conversion from type &apos;{0}&apos; to type &apos;{1}&apos; failed. See the inner exception for more information..
+ /// </summary>
+ internal static string ValueProviderResult_ConversionThrew {
+ get {
+ return ResourceManager.GetString("ValueProviderResult_ConversionThrew", resourceCulture);
+ }
+ }
+
+ /// <summary>
+ /// Looks up a localized string similar to The parameter conversion from type &apos;{0}&apos; to type &apos;{1}&apos; failed because no type converter can convert between these types..
+ /// </summary>
+ internal static string ValueProviderResult_NoConverterExists {
+ get {
+ return ResourceManager.GetString("ValueProviderResult_NoConverterExists", resourceCulture);
+ }
+ }
+
+ /// <summary>
+ /// Looks up a localized string similar to The model item passed into the dictionary is null, but this dictionary requires a non-null model item of type &apos;{0}&apos;..
+ /// </summary>
+ internal static string ViewDataDictionary_ModelCannotBeNull {
+ get {
+ return ResourceManager.GetString("ViewDataDictionary_ModelCannotBeNull", resourceCulture);
+ }
+ }
+
+ /// <summary>
+ /// Looks up a localized string similar to The model item passed into the dictionary is of type &apos;{0}&apos;, but this dictionary requires a model item of type &apos;{1}&apos;..
+ /// </summary>
+ internal static string ViewDataDictionary_WrongTModelType {
+ get {
+ return ResourceManager.GetString("ViewDataDictionary_WrongTModelType", resourceCulture);
+ }
+ }
+
+ /// <summary>
+ /// Looks up a localized string similar to A ViewMasterPage can be used only with content pages that derive from ViewPage or ViewPage&lt;TModel&gt;..
+ /// </summary>
+ internal static string ViewMasterPage_RequiresViewPage {
+ get {
+ return ResourceManager.GetString("ViewMasterPage_RequiresViewPage", resourceCulture);
+ }
+ }
+
+ /// <summary>
+ /// Looks up a localized string similar to Execution of the child request failed. Please examine the InnerException for more information..
+ /// </summary>
+ internal static string ViewPageHttpHandlerWrapper_ExceptionOccurred {
+ get {
+ return ResourceManager.GetString("ViewPageHttpHandlerWrapper_ExceptionOccurred", resourceCulture);
+ }
+ }
+
+ /// <summary>
+ /// Looks up a localized string similar to A ViewStartPage can be used only with with a page that derives from WebViewPage or another ViewStartPage..
+ /// </summary>
+ internal static string ViewStartPage_RequiresMvcRazorView {
+ get {
+ return ResourceManager.GetString("ViewStartPage_RequiresMvcRazorView", resourceCulture);
+ }
+ }
+
+ /// <summary>
+ /// Looks up a localized string similar to The ViewUserControl &apos;{0}&apos; cannot find an IViewDataContainer object. The ViewUserControl must be inside a ViewPage, a ViewMasterPage, or another ViewUserControl..
+ /// </summary>
+ internal static string ViewUserControl_RequiresViewDataProvider {
+ get {
+ return ResourceManager.GetString("ViewUserControl_RequiresViewDataProvider", resourceCulture);
+ }
+ }
+
+ /// <summary>
+ /// Looks up a localized string similar to A ViewUserControl can be used only in pages that derive from ViewPage or ViewPage&lt;TModel&gt;..
+ /// </summary>
+ internal static string ViewUserControl_RequiresViewPage {
+ get {
+ return ResourceManager.GetString("ViewUserControl_RequiresViewPage", resourceCulture);
+ }
+ }
+
+ /// <summary>
+ /// Looks up a localized string similar to A master name cannot be specified when the view is a ViewUserControl..
+ /// </summary>
+ internal static string WebFormViewEngine_UserControlCannotHaveMaster {
+ get {
+ return ResourceManager.GetString("WebFormViewEngine_UserControlCannotHaveMaster", resourceCulture);
+ }
+ }
+
+ /// <summary>
+ /// Looks up a localized string similar to The view at &apos;{0}&apos; must derive from ViewPage, ViewPage&lt;TModel&gt;, ViewUserControl, or ViewUserControl&lt;TModel&gt;..
+ /// </summary>
+ internal static string WebFormViewEngine_WrongViewBase {
+ get {
+ return ResourceManager.GetString("WebFormViewEngine_WrongViewBase", resourceCulture);
+ }
+ }
+ }
+}
diff --git a/src/System.Web.Mvc/Properties/MvcResources.resx b/src/System.Web.Mvc/Properties/MvcResources.resx
new file mode 100644
index 00000000..5bcc68f5
--- /dev/null
+++ b/src/System.Web.Mvc/Properties/MvcResources.resx
@@ -0,0 +1,439 @@
+<?xml version="1.0" encoding="utf-8"?>
+<root>
+ <!--
+ Microsoft ResX Schema
+
+ Version 2.0
+
+ The primary goals of this format is to allow a simple XML format
+ that is mostly human readable. The generation and parsing of the
+ various data types are done through the TypeConverter classes
+ associated with the data types.
+
+ Example:
+
+ ... ado.net/XML headers & schema ...
+ <resheader name="resmimetype">text/microsoft-resx</resheader>
+ <resheader name="version">2.0</resheader>
+ <resheader name="reader">System.Resources.ResXResourceReader, System.Windows.Forms, ...</resheader>
+ <resheader name="writer">System.Resources.ResXResourceWriter, System.Windows.Forms, ...</resheader>
+ <data name="Name1"><value>this is my long string</value><comment>this is a comment</comment></data>
+ <data name="Color1" type="System.Drawing.Color, System.Drawing">Blue</data>
+ <data name="Bitmap1" mimetype="application/x-microsoft.net.object.binary.base64">
+ <value>[base64 mime encoded serialized .NET Framework object]</value>
+ </data>
+ <data name="Icon1" type="System.Drawing.Icon, System.Drawing" mimetype="application/x-microsoft.net.object.bytearray.base64">
+ <value>[base64 mime encoded string representing a byte array form of the .NET Framework object]</value>
+ <comment>This is a comment</comment>
+ </data>
+
+ There are any number of "resheader" rows that contain simple
+ name/value pairs.
+
+ Each data row contains a name, and value. The row also contains a
+ type or mimetype. Type corresponds to a .NET class that support
+ text/value conversion through the TypeConverter architecture.
+ Classes that don't support this are serialized and stored with the
+ mimetype set.
+
+ The mimetype is used for serialized objects, and tells the
+ ResXResourceReader how to depersist the object. This is currently not
+ extensible. For a given mimetype the value must be set accordingly:
+
+ Note - application/x-microsoft.net.object.binary.base64 is the format
+ that the ResXResourceWriter will generate, however the reader can
+ read any of the formats listed below.
+
+ mimetype: application/x-microsoft.net.object.binary.base64
+ value : The object must be serialized with
+ : System.Runtime.Serialization.Formatters.Binary.BinaryFormatter
+ : and then encoded with base64 encoding.
+
+ mimetype: application/x-microsoft.net.object.soap.base64
+ value : The object must be serialized with
+ : System.Runtime.Serialization.Formatters.Soap.SoapFormatter
+ : and then encoded with base64 encoding.
+
+ mimetype: application/x-microsoft.net.object.bytearray.base64
+ value : The object must be serialized into a byte array
+ : using a System.ComponentModel.TypeConverter
+ : and then encoded with base64 encoding.
+ -->
+ <xsd:schema id="root" xmlns="" xmlns:xsd="http://www.w3.org/2001/XMLSchema" xmlns:msdata="urn:schemas-microsoft-com:xml-msdata">
+ <xsd:import namespace="http://www.w3.org/XML/1998/namespace" />
+ <xsd:element name="root" msdata:IsDataSet="true">
+ <xsd:complexType>
+ <xsd:choice maxOccurs="unbounded">
+ <xsd:element name="metadata">
+ <xsd:complexType>
+ <xsd:sequence>
+ <xsd:element name="value" type="xsd:string" minOccurs="0" />
+ </xsd:sequence>
+ <xsd:attribute name="name" use="required" type="xsd:string" />
+ <xsd:attribute name="type" type="xsd:string" />
+ <xsd:attribute name="mimetype" type="xsd:string" />
+ <xsd:attribute ref="xml:space" />
+ </xsd:complexType>
+ </xsd:element>
+ <xsd:element name="assembly">
+ <xsd:complexType>
+ <xsd:attribute name="alias" type="xsd:string" />
+ <xsd:attribute name="name" type="xsd:string" />
+ </xsd:complexType>
+ </xsd:element>
+ <xsd:element name="data">
+ <xsd:complexType>
+ <xsd:sequence>
+ <xsd:element name="value" type="xsd:string" minOccurs="0" msdata:Ordinal="1" />
+ <xsd:element name="comment" type="xsd:string" minOccurs="0" msdata:Ordinal="2" />
+ </xsd:sequence>
+ <xsd:attribute name="name" type="xsd:string" use="required" msdata:Ordinal="1" />
+ <xsd:attribute name="type" type="xsd:string" msdata:Ordinal="3" />
+ <xsd:attribute name="mimetype" type="xsd:string" msdata:Ordinal="4" />
+ <xsd:attribute ref="xml:space" />
+ </xsd:complexType>
+ </xsd:element>
+ <xsd:element name="resheader">
+ <xsd:complexType>
+ <xsd:sequence>
+ <xsd:element name="value" type="xsd:string" minOccurs="0" msdata:Ordinal="1" />
+ </xsd:sequence>
+ <xsd:attribute name="name" type="xsd:string" use="required" />
+ </xsd:complexType>
+ </xsd:element>
+ </xsd:choice>
+ </xsd:complexType>
+ </xsd:element>
+ </xsd:schema>
+ <resheader name="resmimetype">
+ <value>text/microsoft-resx</value>
+ </resheader>
+ <resheader name="version">
+ <value>2.0</value>
+ </resheader>
+ <resheader name="reader">
+ <value>System.Resources.ResXResourceReader, System.Windows.Forms, Version=4.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089</value>
+ </resheader>
+ <resheader name="writer">
+ <value>System.Resources.ResXResourceWriter, System.Windows.Forms, Version=4.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089</value>
+ </resheader>
+ <data name="ActionMethodSelector_AmbiguousMatch" xml:space="preserve">
+ <value>The current request for action '{0}' on controller type '{1}' is ambiguous between the following action methods:{2}</value>
+ </data>
+ <data name="Common_NoRouteMatched" xml:space="preserve">
+ <value>No route in the route table matches the supplied values.</value>
+ </data>
+ <data name="Common_NullOrEmpty" xml:space="preserve">
+ <value>Value cannot be null or empty.</value>
+ </data>
+ <data name="Common_PartialViewNotFound" xml:space="preserve">
+ <value>The partial view '{0}' was not found or no view engine supports the searched locations. The following locations were searched:{1}</value>
+ </data>
+ <data name="Common_PropertyCannotBeNullOrEmpty" xml:space="preserve">
+ <value>The property '{0}' cannot be null or empty.</value>
+ </data>
+ <data name="Common_ViewNotFound" xml:space="preserve">
+ <value>The view '{0}' or its master was not found or no view engine supports the searched locations. The following locations were searched:{1}</value>
+ </data>
+ <data name="ControllerBuilder_ErrorCreatingControllerFactory" xml:space="preserve">
+ <value>An error occurred when trying to create the IControllerFactory '{0}'. Make sure that the controller factory has a public parameterless constructor.</value>
+ </data>
+ <data name="ControllerBuilder_FactoryReturnedNull" xml:space="preserve">
+ <value>The IControllerFactory '{0}' did not return a controller for the name '{1}'.</value>
+ </data>
+ <data name="ControllerBuilder_MissingIControllerFactory" xml:space="preserve">
+ <value>The controller factory type '{0}' must implement the IControllerFactory interface.</value>
+ </data>
+ <data name="Controller_UnknownAction" xml:space="preserve">
+ <value>A public action method '{0}' was not found on controller '{1}'.</value>
+ </data>
+ <data name="DefaultControllerFactory_ErrorCreatingController" xml:space="preserve">
+ <value>An error occurred when trying to create a controller of type '{0}'. Make sure that the controller has a parameterless public constructor.</value>
+ </data>
+ <data name="DefaultControllerFactory_NoControllerFound" xml:space="preserve">
+ <value>The controller for path '{0}' was not found or does not implement IController.</value>
+ </data>
+ <data name="DefaultControllerFactory_TypeDoesNotSubclassControllerBase" xml:space="preserve">
+ <value>The controller type '{0}' must implement IController.</value>
+ </data>
+ <data name="ValueProviderResult_ConversionThrew" xml:space="preserve">
+ <value>The parameter conversion from type '{0}' to type '{1}' failed. See the inner exception for more information.</value>
+ </data>
+ <data name="ValueProviderResult_NoConverterExists" xml:space="preserve">
+ <value>The parameter conversion from type '{0}' to type '{1}' failed because no type converter can convert between these types.</value>
+ </data>
+ <data name="ExceptionViewAttribute_NonExceptionType" xml:space="preserve">
+ <value>The type '{0}' does not inherit from Exception.</value>
+ </data>
+ <data name="FilterAttribute_OrderOutOfRange" xml:space="preserve">
+ <value>Order must be greater than or equal to -1.</value>
+ </data>
+ <data name="HtmlHelper_MissingSelectData" xml:space="preserve">
+ <value>There is no ViewData item of type '{1}' that has the key '{0}'.</value>
+ </data>
+ <data name="HtmlHelper_TextAreaParameterOutOfRange" xml:space="preserve">
+ <value>The value must be greater than or equal to zero.</value>
+ </data>
+ <data name="HtmlHelper_WrongSelectDataType" xml:space="preserve">
+ <value>The ViewData item that has the key '{0}' is of type '{1}' but must be of type '{2}'.</value>
+ </data>
+ <data name="ModelBinderAttribute_ErrorCreatingModelBinder" xml:space="preserve">
+ <value>An error occurred when trying to create the IModelBinder '{0}'. Make sure that the binder has a public parameterless constructor.</value>
+ </data>
+ <data name="ModelBinderAttribute_TypeNotIModelBinder" xml:space="preserve">
+ <value>The type '{0}' does not implement the IModelBinder interface.</value>
+ </data>
+ <data name="ModelBinderDictionary_MultipleAttributes" xml:space="preserve">
+ <value>The type '{0}' contains multiple attributes that inherit from CustomModelBinderAttribute.</value>
+ </data>
+ <data name="SessionStateTempDataProvider_SessionStateDisabled" xml:space="preserve">
+ <value>The SessionStateTempDataProvider class requires session state to be enabled.</value>
+ </data>
+ <data name="ViewDataDictionary_WrongTModelType" xml:space="preserve">
+ <value>The model item passed into the dictionary is of type '{0}', but this dictionary requires a model item of type '{1}'.</value>
+ </data>
+ <data name="ViewMasterPage_RequiresViewPage" xml:space="preserve">
+ <value>A ViewMasterPage can be used only with content pages that derive from ViewPage or ViewPage&lt;TModel&gt;.</value>
+ </data>
+ <data name="ViewUserControl_RequiresViewDataProvider" xml:space="preserve">
+ <value>The ViewUserControl '{0}' cannot find an IViewDataContainer object. The ViewUserControl must be inside a ViewPage, a ViewMasterPage, or another ViewUserControl.</value>
+ </data>
+ <data name="ViewUserControl_RequiresViewPage" xml:space="preserve">
+ <value>A ViewUserControl can be used only in pages that derive from ViewPage or ViewPage&lt;TModel&gt;.</value>
+ </data>
+ <data name="WebFormViewEngine_UserControlCannotHaveMaster" xml:space="preserve">
+ <value>A master name cannot be specified when the view is a ViewUserControl.</value>
+ </data>
+ <data name="WebFormViewEngine_WrongViewBase" xml:space="preserve">
+ <value>The view at '{0}' must derive from ViewPage, ViewPage&lt;TModel&gt;, ViewUserControl, or ViewUserControl&lt;TModel&gt;.</value>
+ </data>
+ <data name="Common_ValueNotValidForProperty" xml:space="preserve">
+ <value>The value '{0}' is invalid.</value>
+ </data>
+ <data name="ActionMethodSelector_AmbiguousMatchType" xml:space="preserve">
+ <value>{0} on type {1}</value>
+ </data>
+ <data name="Controller_UpdateModel_UpdateUnsuccessful" xml:space="preserve">
+ <value>The model of type '{0}' could not be updated.</value>
+ </data>
+ <data name="DefaultModelBinder_ValueRequired" xml:space="preserve">
+ <value>A value is required.</value>
+ </data>
+ <data name="ReflectedActionDescriptor_ParameterCannotBeNull" xml:space="preserve">
+ <value>The parameters dictionary contains a null entry for parameter '{0}' of non-nullable type '{1}' for method '{2}' in '{3}'. An optional parameter must be a reference type, a nullable type, or be declared as an optional parameter.</value>
+ </data>
+ <data name="ReflectedActionDescriptor_ParameterNotInDictionary" xml:space="preserve">
+ <value>The parameters dictionary does not contain an entry for parameter '{0}' of type '{1}' for method '{2}' in '{3}'. The dictionary must contain an entry for each parameter, including parameters that have null values.</value>
+ </data>
+ <data name="ReflectedActionDescriptor_ParameterValueHasWrongType" xml:space="preserve">
+ <value>The parameters dictionary contains an invalid entry for parameter '{0}' for method '{1}' in '{2}'. The dictionary contains a value of type '{3}', but the parameter requires a value of type '{4}'.</value>
+ </data>
+ <data name="ReflectedParameterBindingInfo_MultipleConverterAttributes" xml:space="preserve">
+ <value>The parameter '{0}' on method '{1}' contains multiple attributes that inherit from CustomModelBinderAttribute.</value>
+ </data>
+ <data name="ReflectedActionDescriptor_CannotCallInstanceMethodOnNonControllerType" xml:space="preserve">
+ <value>Cannot create a descriptor for instance method '{0}' on type '{1}' because the type does not derive from ControllerBase.</value>
+ </data>
+ <data name="ReflectedActionDescriptor_CannotCallMethodsWithOutOrRefParameters" xml:space="preserve">
+ <value>Cannot call action method '{0}' on controller '{1}' because the parameter '{2}' is passed by reference.</value>
+ </data>
+ <data name="ReflectedActionDescriptor_CannotCallOpenGenericMethods" xml:space="preserve">
+ <value>Cannot call action method '{0}' on controller '{1}' because the action method is a generic method.</value>
+ </data>
+ <data name="DefaultViewLocationCache_NegativeTimeSpan" xml:space="preserve">
+ <value>The number of ticks for the TimeSpan value must be greater than or equal to 0.</value>
+ </data>
+ <data name="DefaultModelBinder_ValueInvalid" xml:space="preserve">
+ <value>The value '{0}' is not valid for {1}.</value>
+ </data>
+ <data name="TemplateHelpers_TemplateLimitations" xml:space="preserve">
+ <value>Templates can be used only with field access, property access, single-dimension array index, or single-parameter custom indexer expressions.</value>
+ </data>
+ <data name="Common_TriState_False" xml:space="preserve">
+ <value>False</value>
+ </data>
+ <data name="Common_TriState_NotSet" xml:space="preserve">
+ <value>Not Set</value>
+ </data>
+ <data name="Common_TriState_True" xml:space="preserve">
+ <value>True</value>
+ </data>
+ <data name="ControllerBase_CannotHandleMultipleRequests" xml:space="preserve">
+ <value>A single instance of controller '{0}' cannot be used to handle multiple requests. If a custom controller factory is in use, make sure that it creates a new instance of the controller for each request.</value>
+ </data>
+ <data name="Common_PropertyNotFound" xml:space="preserve">
+ <value>The property {0}.{1} could not be found.</value>
+ </data>
+ <data name="DataAnnotationsModelMetadataProvider_UnknownProperty" xml:space="preserve">
+ <value>{0} has a DisplayColumn attribute for {1}, but property {1} does not exist.</value>
+ </data>
+ <data name="DataAnnotationsModelMetadataProvider_UnreadableProperty" xml:space="preserve">
+ <value>{0} has a DisplayColumn attribute for {1}, but property {1} does not have a public getter.</value>
+ </data>
+ <data name="TemplateHelpers_NoTemplate" xml:space="preserve">
+ <value>Unable to locate an appropriate template for type {0}.</value>
+ </data>
+ <data name="RequireHttpsAttribute_MustUseSsl" xml:space="preserve">
+ <value>The requested resource can only be accessed via SSL.</value>
+ </data>
+ <data name="HtmlHelper_InvalidHttpVerb" xml:space="preserve">
+ <value>The specified HttpVerbs value is not supported. The supported values are Delete, Head, and Put.</value>
+ </data>
+ <data name="HtmlHelper_InvalidHttpMethod" xml:space="preserve">
+ <value>The GET and POST HTTP methods are not supported.</value>
+ </data>
+ <data name="JsonRequest_GetNotAllowed" xml:space="preserve">
+ <value>This request has been blocked because sensitive information could be disclosed to third party web sites when this is used in a GET request. To allow GET requests, set JsonRequestBehavior to AllowGet.</value>
+ </data>
+ <data name="ModelMetadata_PropertyNotSettable" xml:space="preserve">
+ <value>This property setter is obsolete, because its value is derived from ModelMetadata.Model now.</value>
+ </data>
+ <data name="ViewDataDictionary_ModelCannotBeNull" xml:space="preserve">
+ <value>The model item passed into the dictionary is null, but this dictionary requires a non-null model item of type '{0}'.</value>
+ </data>
+ <data name="Common_TypeMustDriveFromType" xml:space="preserve">
+ <value>The type {0} must derive from {1}</value>
+ </data>
+ <data name="DataAnnotationsModelValidatorProvider_ConstructorRequirements" xml:space="preserve">
+ <value>The type {0} must have a public constructor which accepts three parameters of types {1}, {2}, and {3}</value>
+ </data>
+ <data name="ViewPageHttpHandlerWrapper_ExceptionOccurred" xml:space="preserve">
+ <value>Execution of the child request failed. Please examine the InnerException for more information.</value>
+ </data>
+ <data name="RedirectAction_CannotRedirectInChildAction" xml:space="preserve">
+ <value>Child actions are not allowed to perform redirect actions.</value>
+ </data>
+ <data name="AsyncCommon_AsyncResultAlreadyConsumed" xml:space="preserve">
+ <value>The provided IAsyncResult has already been consumed.</value>
+ </data>
+ <data name="AsyncCommon_InvalidAsyncResult" xml:space="preserve">
+ <value>The provided IAsyncResult is not valid for this method.</value>
+ </data>
+ <data name="SynchronizationContextUtil_ExceptionThrown" xml:space="preserve">
+ <value>An operation that crossed a synchronization context failed. See the inner exception for more information.</value>
+ </data>
+ <data name="AsyncCommon_ControllerMustImplementIAsyncManagerContainer" xml:space="preserve">
+ <value>The controller of type '{0}' must subclass AsyncController or implement the IAsyncManagerContainer interface.</value>
+ </data>
+ <data name="AsyncCommon_InvalidTimeout" xml:space="preserve">
+ <value>The timeout value must be non-negative or Timeout.Infinite.</value>
+ </data>
+ <data name="AsyncActionMethodSelector_AmbiguousMethodMatch" xml:space="preserve">
+ <value>Lookup for method '{0}' on controller type '{1}' failed because of an ambiguity between the following methods:{2}</value>
+ </data>
+ <data name="AsyncActionMethodSelector_CouldNotFindMethod" xml:space="preserve">
+ <value>Could not locate a method named '{0}' on controller type {1}.</value>
+ </data>
+ <data name="ChildActionOnlyAttribute_MustBeInChildRequest" xml:space="preserve">
+ <value>The action '{0}' is accessible only by a child request.</value>
+ </data>
+ <data name="Templates_TypeMustImplementIEnumerable" xml:space="preserve">
+ <value>The Collection template was used with an object of type '{0}', which does not implement System.IEnumerable.</value>
+ </data>
+ <data name="TypeCache_DoNotModify" xml:space="preserve">
+ <value>This file is automatically generated. Please do not modify the contents of this file.</value>
+ </data>
+ <data name="ClientDataTypeModelValidatorProvider_FieldMustBeNumeric" xml:space="preserve">
+ <value>The field {0} must be a number.</value>
+ </data>
+ <data name="ExpressionHelper_InvalidIndexerExpression" xml:space="preserve">
+ <value>The expression compiler was unable to evaluate the indexer expression '{0}' because it references the model parameter '{1}' which is unavailable.</value>
+ </data>
+ <data name="Controller_Validate_ValidationFailed" xml:space="preserve">
+ <value>The model of type '{0}' is not valid.</value>
+ </data>
+ <data name="DefaultControllerFactory_ControllerNameAmbiguous_WithoutRouteUrl" xml:space="preserve">
+ <value>Multiple types were found that match the controller named '{0}'. This can happen if the route that services this request does not specify namespaces to search for a controller that matches the request. If this is the case, register this route by calling an overload of the 'MapRoute' method that takes a 'namespaces' parameter.
+
+The request for '{0}' has found the following matching controllers:{1}</value>
+ </data>
+ <data name="DefaultControllerFactory_ControllerNameAmbiguous_WithRouteUrl" xml:space="preserve">
+ <value>Multiple types were found that match the controller named '{0}'. This can happen if the route that services this request ('{1}') does not specify namespaces to search for a controller that matches the request. If this is the case, register this route by calling an overload of the 'MapRoute' method that takes a 'namespaces' parameter.
+
+The request for '{0}' has found the following matching controllers:{2}</value>
+ </data>
+ <data name="DataAnnotationsModelValidatorProvider_ValidatableConstructorRequirements" xml:space="preserve">
+ <value>The type {0} must have a public constructor which accepts two parameters of types {1} and {2}.</value>
+ </data>
+ <data name="ValidatableObjectAdapter_IncompatibleType" xml:space="preserve">
+ <value>The model object inside the metadata claimed to be compatible with {0}, but was actually {1}.</value>
+ </data>
+ <data name="CshtmlView_ViewCouldNotBeCreated" xml:space="preserve">
+ <value>The view found at '{0}' was not created.</value>
+ </data>
+ <data name="CshtmlView_WrongViewBase" xml:space="preserve">
+ <value>The view at '{0}' must derive from WebViewPage, or WebViewPage&lt;TModel&gt;.</value>
+ </data>
+ <data name="ReflectedActionDescriptor_CannotCallStaticMethod" xml:space="preserve">
+ <value>Cannot call action method '{0}' on controller '{1}' because the action method is a static method.</value>
+ </data>
+ <data name="MvcRazorCodeParser_ModelKeywordMustBeFollowedByTypeName" xml:space="preserve">
+ <value>The '{0}' keyword must be followed by a type name on the same line.</value>
+ </data>
+ <data name="MvcRazorCodeParser_CannotHaveModelAndInheritsKeyword" xml:space="preserve">
+ <value>The 'inherits' keyword is not allowed when a '{0}' keyword is used.</value>
+ </data>
+ <data name="MvcRazorCodeParser_OnlyOneModelStatementIsAllowed" xml:space="preserve">
+ <value>Only one '{0}' statement is allowed in a file.</value>
+ </data>
+ <data name="SingleServiceResolver_CannotRegisterTwoInstances" xml:space="preserve">
+ <value>An instance of {0} was found in the resolver as well as a custom registered provider in {1}. Please set only one or the other.</value>
+ </data>
+ <data name="DependencyResolver_DoesNotImplementICommonServiceLocator" xml:space="preserve">
+ <value>The type {0} does not appear to implement Microsoft.Practices.ServiceLocation.IServiceLocator.</value>
+ </data>
+ <data name="ViewStartPage_RequiresMvcRazorView" xml:space="preserve">
+ <value>A ViewStartPage can be used only with with a page that derives from WebViewPage or another ViewStartPage.</value>
+ </data>
+ <data name="ControllerBase_CannotExecuteWithNullHttpContext" xml:space="preserve">
+ <value>Cannot execute Controller with a null HttpContext.</value>
+ </data>
+ <data name="CompareAttribute_MustMatch" xml:space="preserve">
+ <value>'{0}' and '{1}' do not match.</value>
+ </data>
+ <data name="RemoteAttribute_RemoteValidationFailed" xml:space="preserve">
+ <value>'{0}' is invalid.</value>
+ </data>
+ <data name="RemoteAttribute_NoUrlFound" xml:space="preserve">
+ <value>No url for remote validation could be found.</value>
+ </data>
+ <data name="AuthorizeAttribute_CannotUseWithinChildActionCache" xml:space="preserve">
+ <value>AuthorizeAttribute cannot be used within a child action caching block.</value>
+ </data>
+ <data name="OutputCacheAttribute_InvalidDuration" xml:space="preserve">
+ <value>Duration must be a positive number.</value>
+ </data>
+ <data name="OutputCacheAttribute_InvalidVaryByParam" xml:space="preserve">
+ <value>VaryByParam must be '*', 'none', or a semicolon-delimited list of keys.</value>
+ </data>
+ <data name="OutputCacheAttribute_ChildAction_UnsupportedSetting" xml:space="preserve">
+ <value>OutputCacheAttribute for child actions only supports Duration, VaryByCustom, and VaryByParam values. Please do not set CacheProfile, Location, NoStore, SqlDependency, VaryByContentEncoding, or VaryByHeader values for child actions.</value>
+ </data>
+ <data name="OutputCacheAttribute_CannotNestChildCache" xml:space="preserve">
+ <value>OutputCacheAttribute is not allowed on child actions which are children of an already cached child action.</value>
+ </data>
+ <data name="CompareAttribute_UnknownProperty" xml:space="preserve">
+ <value>Could not find a property named {0}.</value>
+ </data>
+ <data name="HtmlHelper_SelectExpressionNotEnumerable" xml:space="preserve">
+ <value>The parameter '{0}' must evaluate to an IEnumerable when multiple selection is allowed.</value>
+ </data>
+ <data name="MvcRouteHandler_RouteValuesHasNoController" xml:space="preserve">
+ <value>The matched route does not include a 'controller' route value, which is required.</value>
+ </data>
+ <data name="ClientDataTypeModelValidatorProvider_FieldMustBeDate" xml:space="preserve">
+ <value>The field {0} must be a date.</value>
+ </data>
+ <data name="TaskAsyncActionDescriptor_CannotExecuteSynchronously" xml:space="preserve">
+ <value>The asynchronous action method '{0}' returns a Task, which cannot be executed synchronously.</value>
+ </data>
+ <data name="AsyncActionDescriptor_CannotExecuteSynchronously" xml:space="preserve">
+ <value>The asynchronous action method '{0}' cannot be executed synchronously.</value>
+ </data>
+ <data name="MvcForm_ConstructorObsolete" xml:space="preserve">
+ <value>This constructor is obsolete, because its functionality has been moved to MvcForm(ViewContext) now.</value>
+ </data>
+ <data name="JsonValueProviderFactory_RequestTooLarge" xml:space="preserve">
+ <value>The JSON request was too large to be deserialized.</value>
+ </data>
+</root> \ No newline at end of file
diff --git a/src/System.Web.Mvc/QueryStringValueProvider.cs b/src/System.Web.Mvc/QueryStringValueProvider.cs
new file mode 100644
index 00000000..3a2d34ba
--- /dev/null
+++ b/src/System.Web.Mvc/QueryStringValueProvider.cs
@@ -0,0 +1,21 @@
+using System.Globalization;
+using System.Web.Helpers;
+
+namespace System.Web.Mvc
+{
+ public sealed class QueryStringValueProvider : NameValueCollectionValueProvider
+ {
+ // QueryString should use the invariant culture since it's part of the URL, and the URL should be
+ // interpreted in a uniform fashion regardless of the origin of a particular request.
+ public QueryStringValueProvider(ControllerContext controllerContext)
+ : this(controllerContext, new UnvalidatedRequestValuesWrapper(controllerContext.HttpContext.Request.Unvalidated()))
+ {
+ }
+
+ // For unit testing
+ internal QueryStringValueProvider(ControllerContext controllerContext, IUnvalidatedRequestValues unvalidatedValues)
+ : base(controllerContext.HttpContext.Request.QueryString, unvalidatedValues.QueryString, CultureInfo.InvariantCulture)
+ {
+ }
+ }
+}
diff --git a/src/System.Web.Mvc/QueryStringValueProviderFactory.cs b/src/System.Web.Mvc/QueryStringValueProviderFactory.cs
new file mode 100644
index 00000000..eaa0f696
--- /dev/null
+++ b/src/System.Web.Mvc/QueryStringValueProviderFactory.cs
@@ -0,0 +1,30 @@
+using System.Web.Helpers;
+
+namespace System.Web.Mvc
+{
+ public sealed class QueryStringValueProviderFactory : ValueProviderFactory
+ {
+ private readonly UnvalidatedRequestValuesAccessor _unvalidatedValuesAccessor;
+
+ public QueryStringValueProviderFactory()
+ : this(null)
+ {
+ }
+
+ // For unit testing
+ internal QueryStringValueProviderFactory(UnvalidatedRequestValuesAccessor unvalidatedValuesAccessor)
+ {
+ _unvalidatedValuesAccessor = unvalidatedValuesAccessor ?? (cc => new UnvalidatedRequestValuesWrapper(cc.HttpContext.Request.Unvalidated()));
+ }
+
+ public override IValueProvider GetValueProvider(ControllerContext controllerContext)
+ {
+ if (controllerContext == null)
+ {
+ throw new ArgumentNullException("controllerContext");
+ }
+
+ return new QueryStringValueProvider(controllerContext, _unvalidatedValuesAccessor(controllerContext));
+ }
+ }
+}
diff --git a/src/System.Web.Mvc/RangeAttributeAdapter.cs b/src/System.Web.Mvc/RangeAttributeAdapter.cs
new file mode 100644
index 00000000..d0b41660
--- /dev/null
+++ b/src/System.Web.Mvc/RangeAttributeAdapter.cs
@@ -0,0 +1,19 @@
+using System.Collections.Generic;
+using System.ComponentModel.DataAnnotations;
+
+namespace System.Web.Mvc
+{
+ public class RangeAttributeAdapter : DataAnnotationsModelValidator<RangeAttribute>
+ {
+ public RangeAttributeAdapter(ModelMetadata metadata, ControllerContext context, RangeAttribute attribute)
+ : base(metadata, context, attribute)
+ {
+ }
+
+ public override IEnumerable<ModelClientValidationRule> GetClientValidationRules()
+ {
+ string errorMessage = ErrorMessage; // Per Dev10 Bug #923283, need to make sure ErrorMessage is called before Minimum/Maximum
+ return new[] { new ModelClientValidationRangeRule(errorMessage, Attribute.Minimum, Attribute.Maximum) };
+ }
+ }
+}
diff --git a/src/System.Web.Mvc/Razor/MvcCSharpRazorCodeGenerator.cs b/src/System.Web.Mvc/Razor/MvcCSharpRazorCodeGenerator.cs
new file mode 100644
index 00000000..b09a95a9
--- /dev/null
+++ b/src/System.Web.Mvc/Razor/MvcCSharpRazorCodeGenerator.cs
@@ -0,0 +1,30 @@
+using System.CodeDom;
+using System.Web.Razor;
+using System.Web.Razor.Generator;
+
+namespace System.Web.Mvc.Razor
+{
+ internal class MvcCSharpRazorCodeGenerator : CSharpRazorCodeGenerator
+ {
+ private const string DefaultModelTypeName = "dynamic";
+
+ public MvcCSharpRazorCodeGenerator(string className, string rootNamespaceName, string sourceFileName, RazorEngineHost host)
+ : base(className, rootNamespaceName, sourceFileName, host)
+ {
+ var mvcHost = host as MvcWebPageRazorHost;
+ if (mvcHost != null && !mvcHost.IsSpecialPage)
+ {
+ // set the default model type to "dynamic" (Dev10 bug 935656)
+ // don't set it for "special" pages (such as "_viewStart.cshtml")
+ SetBaseType(DefaultModelTypeName);
+ }
+ }
+
+ private void SetBaseType(string modelTypeName)
+ {
+ var baseType = new CodeTypeReference(Context.Host.DefaultBaseClass + "<" + modelTypeName + ">");
+ Context.GeneratedClass.BaseTypes.Clear();
+ Context.GeneratedClass.BaseTypes.Add(baseType);
+ }
+ }
+}
diff --git a/src/System.Web.Mvc/Razor/MvcCSharpRazorCodeParser.cs b/src/System.Web.Mvc/Razor/MvcCSharpRazorCodeParser.cs
new file mode 100644
index 00000000..e2d30351
--- /dev/null
+++ b/src/System.Web.Mvc/Razor/MvcCSharpRazorCodeParser.cs
@@ -0,0 +1,68 @@
+using System.Globalization;
+using System.Web.Mvc.Properties;
+using System.Web.Razor.Generator;
+using System.Web.Razor.Parser;
+using System.Web.Razor.Text;
+
+namespace System.Web.Mvc.Razor
+{
+ public class MvcCSharpRazorCodeParser : CSharpCodeParser
+ {
+ private const string ModelKeyword = "model";
+ private const string GenericTypeFormatString = "{0}<{1}>";
+ private SourceLocation? _endInheritsLocation;
+ private bool _modelStatementFound;
+
+ public MvcCSharpRazorCodeParser()
+ {
+ MapDirectives(ModelDirective, ModelKeyword);
+ }
+
+ protected override void InheritsDirective()
+ {
+ // Verify we're on the right keyword and accept
+ AssertDirective(SyntaxConstants.CSharp.InheritsKeyword);
+ AcceptAndMoveNext();
+ _endInheritsLocation = CurrentLocation;
+
+ InheritsDirectiveCore();
+ CheckForInheritsAndModelStatements();
+ }
+
+ private void CheckForInheritsAndModelStatements()
+ {
+ if (_modelStatementFound && _endInheritsLocation.HasValue)
+ {
+ Context.OnError(_endInheritsLocation.Value, String.Format(CultureInfo.CurrentCulture, MvcResources.MvcRazorCodeParser_CannotHaveModelAndInheritsKeyword, ModelKeyword));
+ }
+ }
+
+ protected virtual void ModelDirective()
+ {
+ // Verify we're on the right keyword and accept
+ AssertDirective(ModelKeyword);
+ AcceptAndMoveNext();
+
+ SourceLocation endModelLocation = CurrentLocation;
+
+ BaseTypeDirective(
+ String.Format(CultureInfo.CurrentCulture,
+ MvcResources.MvcRazorCodeParser_ModelKeywordMustBeFollowedByTypeName, ModelKeyword),
+ CreateModelCodeGenerator);
+
+ if (_modelStatementFound)
+ {
+ Context.OnError(endModelLocation, String.Format(CultureInfo.CurrentCulture, MvcResources.MvcRazorCodeParser_OnlyOneModelStatementIsAllowed, ModelKeyword));
+ }
+
+ _modelStatementFound = true;
+
+ CheckForInheritsAndModelStatements();
+ }
+
+ private SpanCodeGenerator CreateModelCodeGenerator(string model)
+ {
+ return new SetModelTypeCodeGenerator(model, GenericTypeFormatString);
+ }
+ }
+}
diff --git a/src/System.Web.Mvc/Razor/MvcVBRazorCodeParser.cs b/src/System.Web.Mvc/Razor/MvcVBRazorCodeParser.cs
new file mode 100644
index 00000000..d2e33479
--- /dev/null
+++ b/src/System.Web.Mvc/Razor/MvcVBRazorCodeParser.cs
@@ -0,0 +1,93 @@
+using System.Globalization;
+using System.Linq;
+using System.Web.Mvc.Properties;
+using System.Web.Razor.Generator;
+using System.Web.Razor.Parser;
+using System.Web.Razor.Parser.SyntaxTree;
+using System.Web.Razor.Text;
+using System.Web.Razor.Tokenizer.Symbols;
+
+namespace System.Web.Mvc.Razor
+{
+ public class MvcVBRazorCodeParser : VBCodeParser
+ {
+ internal const string ModelTypeKeyword = "ModelType";
+ private const string GenericTypeFormatString = "{0}(Of {1})";
+ private SourceLocation? _endInheritsLocation;
+ private bool _modelStatementFound;
+
+ public MvcVBRazorCodeParser()
+ {
+ MapDirective(ModelTypeKeyword, ModelTypeDirective);
+ }
+
+ protected override bool InheritsStatement()
+ {
+ // Verify we're on the right keyword and accept
+ Assert(VBKeyword.Inherits);
+ VBSymbol inherits = CurrentSymbol;
+ NextToken();
+ _endInheritsLocation = CurrentLocation;
+ PutCurrentBack();
+ PutBack(inherits);
+ EnsureCurrent();
+
+ bool result = base.InheritsStatement();
+ CheckForInheritsAndModelStatements();
+ return result;
+ }
+
+ private void CheckForInheritsAndModelStatements()
+ {
+ if (_modelStatementFound && _endInheritsLocation.HasValue)
+ {
+ Context.OnError(_endInheritsLocation.Value, String.Format(CultureInfo.CurrentCulture, MvcResources.MvcRazorCodeParser_CannotHaveModelAndInheritsKeyword, ModelTypeKeyword));
+ }
+ }
+
+ protected virtual bool ModelTypeDirective()
+ {
+ AssertDirective(ModelTypeKeyword);
+
+ Span.CodeGenerator = SpanCodeGenerator.Null;
+ Context.CurrentBlock.Type = BlockType.Directive;
+
+ AcceptAndMoveNext();
+ SourceLocation endModelLocation = CurrentLocation;
+
+ if (At(VBSymbolType.WhiteSpace))
+ {
+ Span.EditHandler.AcceptedCharacters = AcceptedCharacters.None;
+ }
+
+ AcceptWhile(VBSymbolType.WhiteSpace);
+ Output(SpanKind.MetaCode);
+
+ if (_modelStatementFound)
+ {
+ Context.OnError(endModelLocation, String.Format(CultureInfo.CurrentCulture, MvcResources.MvcRazorCodeParser_OnlyOneModelStatementIsAllowed, ModelTypeKeyword));
+ }
+ _modelStatementFound = true;
+
+ if (EndOfFile || At(VBSymbolType.WhiteSpace) || At(VBSymbolType.NewLine))
+ {
+ Context.OnError(endModelLocation, MvcResources.MvcRazorCodeParser_ModelKeywordMustBeFollowedByTypeName, ModelTypeKeyword);
+ }
+
+ // Just accept to a newline
+ AcceptUntil(VBSymbolType.NewLine);
+ if (!Context.DesignTimeMode)
+ {
+ // We want the newline to be treated as code, but it causes issues at design-time.
+ Optional(VBSymbolType.NewLine);
+ }
+
+ string baseType = String.Concat(Span.Symbols.Select(s => s.Content)).Trim();
+ Span.CodeGenerator = new SetModelTypeCodeGenerator(baseType, GenericTypeFormatString);
+
+ CheckForInheritsAndModelStatements();
+ Output(SpanKind.Code);
+ return false;
+ }
+ }
+}
diff --git a/src/System.Web.Mvc/Razor/MvcWebPageRazorHost.cs b/src/System.Web.Mvc/Razor/MvcWebPageRazorHost.cs
new file mode 100644
index 00000000..69f058f7
--- /dev/null
+++ b/src/System.Web.Mvc/Razor/MvcWebPageRazorHost.cs
@@ -0,0 +1,64 @@
+using System.Diagnostics;
+using System.Diagnostics.CodeAnalysis;
+using System.Web.Razor.Generator;
+using System.Web.Razor.Parser;
+using System.Web.WebPages.Razor;
+
+namespace System.Web.Mvc.Razor
+{
+ [SuppressMessage("Microsoft.Naming", "CA1702:CompoundWordsShouldBeCasedCorrectly", MessageId = "WebPage", Justification = "The class name is derived from the name of the base type")]
+ public class MvcWebPageRazorHost : WebPageRazorHost
+ {
+ [SuppressMessage("Microsoft.Usage", "CA2214:DoNotCallOverridableMethodsInConstructors", Justification = "The NamespaceImports property should not be virtual. This is a temporary fix.")]
+ public MvcWebPageRazorHost(string virtualPath, string physicalPath)
+ : base(virtualPath, physicalPath)
+ {
+ RegisterSpecialFile(RazorViewEngine.ViewStartFileName, typeof(ViewStartPage));
+
+ DefaultPageBaseClass = typeof(WebViewPage).FullName;
+
+ // REVIEW get rid of the namespace import to not force additional references in default MVC projects
+ GetRidOfNamespace("System.Web.WebPages.Html");
+ }
+
+ public override RazorCodeGenerator DecorateCodeGenerator(RazorCodeGenerator incomingCodeGenerator)
+ {
+ if (incomingCodeGenerator is CSharpRazorCodeGenerator)
+ {
+ return new MvcCSharpRazorCodeGenerator(incomingCodeGenerator.ClassName,
+ incomingCodeGenerator.RootNamespaceName,
+ incomingCodeGenerator.SourceFileName,
+ incomingCodeGenerator.Host);
+ }
+ else
+ {
+ return base.DecorateCodeGenerator(incomingCodeGenerator);
+ }
+ }
+
+ public override ParserBase DecorateCodeParser(ParserBase incomingCodeParser)
+ {
+ if (incomingCodeParser is CSharpCodeParser)
+ {
+ return new MvcCSharpRazorCodeParser();
+ }
+ else if (incomingCodeParser is VBCodeParser)
+ {
+ return new MvcVBRazorCodeParser();
+ }
+ else
+ {
+ return base.DecorateCodeParser(incomingCodeParser);
+ }
+ }
+
+ private void GetRidOfNamespace(string ns)
+ {
+ Debug.Assert(NamespaceImports.Contains(ns), ns + " is not a default namespace anymore");
+ if (NamespaceImports.Contains(ns))
+ {
+ NamespaceImports.Remove(ns);
+ }
+ }
+ }
+}
diff --git a/src/System.Web.Mvc/Razor/SetModelTypeCodeGenerator.cs b/src/System.Web.Mvc/Razor/SetModelTypeCodeGenerator.cs
new file mode 100644
index 00000000..e8640b20
--- /dev/null
+++ b/src/System.Web.Mvc/Razor/SetModelTypeCodeGenerator.cs
@@ -0,0 +1,47 @@
+using System.Globalization;
+using System.Web.Mvc.ExpressionUtil;
+using System.Web.Razor.Generator;
+
+namespace System.Web.Mvc.Razor
+{
+ internal class SetModelTypeCodeGenerator : SetBaseTypeCodeGenerator
+ {
+ private string _genericTypeFormat;
+
+ public SetModelTypeCodeGenerator(string modelType, string genericTypeFormat)
+ : base(modelType)
+ {
+ _genericTypeFormat = genericTypeFormat;
+ }
+
+ protected override string ResolveType(CodeGeneratorContext context, string baseType)
+ {
+ return String.Format(
+ CultureInfo.InvariantCulture,
+ _genericTypeFormat,
+ context.Host.DefaultBaseClass,
+ baseType);
+ }
+
+ public override bool Equals(object obj)
+ {
+ SetModelTypeCodeGenerator other = obj as SetModelTypeCodeGenerator;
+ return other != null &&
+ base.Equals(obj) &&
+ String.Equals(_genericTypeFormat, other._genericTypeFormat, StringComparison.Ordinal);
+ }
+
+ public override int GetHashCode()
+ {
+ var combiner = new HashCodeCombiner();
+ combiner.AddInt32(base.GetHashCode());
+ combiner.AddObject(_genericTypeFormat);
+ return combiner.CombinedHash;
+ }
+
+ public override string ToString()
+ {
+ return "Model:" + BaseType;
+ }
+ }
+}
diff --git a/src/System.Web.Mvc/Razor/StartPageLookupDelegate.cs b/src/System.Web.Mvc/Razor/StartPageLookupDelegate.cs
new file mode 100644
index 00000000..07197610
--- /dev/null
+++ b/src/System.Web.Mvc/Razor/StartPageLookupDelegate.cs
@@ -0,0 +1,7 @@
+using System.Collections.Generic;
+using System.Web.WebPages;
+
+namespace System.Web.Mvc.Razor
+{
+ internal delegate WebPageRenderingBase StartPageLookupDelegate(WebPageRenderingBase page, string fileName, IEnumerable<string> supportedExtensions);
+}
diff --git a/src/System.Web.Mvc/RazorView.cs b/src/System.Web.Mvc/RazorView.cs
new file mode 100644
index 00000000..70b96cb3
--- /dev/null
+++ b/src/System.Web.Mvc/RazorView.cs
@@ -0,0 +1,82 @@
+using System.Collections.Generic;
+using System.Globalization;
+using System.IO;
+using System.Linq;
+using System.Web.Mvc.Properties;
+using System.Web.Mvc.Razor;
+using System.Web.WebPages;
+
+namespace System.Web.Mvc
+{
+ public class RazorView : BuildManagerCompiledView
+ {
+ public RazorView(ControllerContext controllerContext, string viewPath, string layoutPath, bool runViewStartPages, IEnumerable<string> viewStartFileExtensions)
+ : this(controllerContext, viewPath, layoutPath, runViewStartPages, viewStartFileExtensions, null)
+ {
+ }
+
+ public RazorView(ControllerContext controllerContext, string viewPath, string layoutPath, bool runViewStartPages, IEnumerable<string> viewStartFileExtensions, IViewPageActivator viewPageActivator)
+ : base(controllerContext, viewPath, viewPageActivator)
+ {
+ LayoutPath = layoutPath ?? String.Empty;
+ RunViewStartPages = runViewStartPages;
+ StartPageLookup = StartPage.GetStartPage;
+ ViewStartFileExtensions = viewStartFileExtensions ?? Enumerable.Empty<string>();
+ }
+
+ public string LayoutPath { get; private set; }
+
+ public bool RunViewStartPages { get; private set; }
+
+ internal StartPageLookupDelegate StartPageLookup { get; set; }
+
+ internal IVirtualPathFactory VirtualPathFactory { get; set; }
+
+ internal DisplayModeProvider DisplayModeProvider { get; set; }
+
+ public IEnumerable<string> ViewStartFileExtensions { get; private set; }
+
+ protected override void RenderView(ViewContext viewContext, TextWriter writer, object instance)
+ {
+ if (writer == null)
+ {
+ throw new ArgumentNullException("writer");
+ }
+
+ WebViewPage webViewPage = instance as WebViewPage;
+ if (webViewPage == null)
+ {
+ throw new InvalidOperationException(
+ String.Format(
+ CultureInfo.CurrentCulture,
+ MvcResources.CshtmlView_WrongViewBase,
+ ViewPath));
+ }
+
+ // An overriden master layout might have been specified when the ViewActionResult got returned.
+ // We need to hold on to it so that we can set it on the inner page once it has executed.
+ webViewPage.OverridenLayoutPath = LayoutPath;
+ webViewPage.VirtualPath = ViewPath;
+ webViewPage.ViewContext = viewContext;
+ webViewPage.ViewData = viewContext.ViewData;
+
+ webViewPage.InitHelpers();
+
+ if (VirtualPathFactory != null)
+ {
+ webViewPage.VirtualPathFactory = VirtualPathFactory;
+ }
+ if (DisplayModeProvider != null)
+ {
+ webViewPage.DisplayModeProvider = DisplayModeProvider;
+ }
+
+ WebPageRenderingBase startPage = null;
+ if (RunViewStartPages)
+ {
+ startPage = StartPageLookup(webViewPage, RazorViewEngine.ViewStartFileName, ViewStartFileExtensions);
+ }
+ webViewPage.ExecutePageHierarchy(new WebPageContext(context: viewContext.HttpContext, page: null, model: null), writer, startPage);
+ }
+ }
+}
diff --git a/src/System.Web.Mvc/RazorViewEngine.cs b/src/System.Web.Mvc/RazorViewEngine.cs
new file mode 100644
index 00000000..9e903be6
--- /dev/null
+++ b/src/System.Web.Mvc/RazorViewEngine.cs
@@ -0,0 +1,85 @@
+namespace System.Web.Mvc
+{
+ public class RazorViewEngine : BuildManagerViewEngine
+ {
+ internal static readonly string ViewStartFileName = "_ViewStart";
+
+ public RazorViewEngine()
+ : this(null)
+ {
+ }
+
+ public RazorViewEngine(IViewPageActivator viewPageActivator)
+ : base(viewPageActivator)
+ {
+ AreaViewLocationFormats = new[]
+ {
+ "~/Areas/{2}/Views/{1}/{0}.cshtml",
+ "~/Areas/{2}/Views/{1}/{0}.vbhtml",
+ "~/Areas/{2}/Views/Shared/{0}.cshtml",
+ "~/Areas/{2}/Views/Shared/{0}.vbhtml"
+ };
+ AreaMasterLocationFormats = new[]
+ {
+ "~/Areas/{2}/Views/{1}/{0}.cshtml",
+ "~/Areas/{2}/Views/{1}/{0}.vbhtml",
+ "~/Areas/{2}/Views/Shared/{0}.cshtml",
+ "~/Areas/{2}/Views/Shared/{0}.vbhtml"
+ };
+ AreaPartialViewLocationFormats = new[]
+ {
+ "~/Areas/{2}/Views/{1}/{0}.cshtml",
+ "~/Areas/{2}/Views/{1}/{0}.vbhtml",
+ "~/Areas/{2}/Views/Shared/{0}.cshtml",
+ "~/Areas/{2}/Views/Shared/{0}.vbhtml"
+ };
+
+ ViewLocationFormats = new[]
+ {
+ "~/Views/{1}/{0}.cshtml",
+ "~/Views/{1}/{0}.vbhtml",
+ "~/Views/Shared/{0}.cshtml",
+ "~/Views/Shared/{0}.vbhtml"
+ };
+ MasterLocationFormats = new[]
+ {
+ "~/Views/{1}/{0}.cshtml",
+ "~/Views/{1}/{0}.vbhtml",
+ "~/Views/Shared/{0}.cshtml",
+ "~/Views/Shared/{0}.vbhtml"
+ };
+ PartialViewLocationFormats = new[]
+ {
+ "~/Views/{1}/{0}.cshtml",
+ "~/Views/{1}/{0}.vbhtml",
+ "~/Views/Shared/{0}.cshtml",
+ "~/Views/Shared/{0}.vbhtml"
+ };
+
+ FileExtensions = new[]
+ {
+ "cshtml",
+ "vbhtml",
+ };
+ }
+
+ protected override IView CreatePartialView(ControllerContext controllerContext, string partialPath)
+ {
+ return new RazorView(controllerContext, partialPath,
+ layoutPath: null, runViewStartPages: false, viewStartFileExtensions: FileExtensions, viewPageActivator: ViewPageActivator)
+ {
+ DisplayModeProvider = DisplayModeProvider
+ };
+ }
+
+ protected override IView CreateView(ControllerContext controllerContext, string viewPath, string masterPath)
+ {
+ var view = new RazorView(controllerContext, viewPath,
+ layoutPath: masterPath, runViewStartPages: true, viewStartFileExtensions: FileExtensions, viewPageActivator: ViewPageActivator)
+ {
+ DisplayModeProvider = DisplayModeProvider
+ };
+ return view;
+ }
+ }
+}
diff --git a/src/System.Web.Mvc/ReaderWriterCache`2.cs b/src/System.Web.Mvc/ReaderWriterCache`2.cs
new file mode 100644
index 00000000..a02b4740
--- /dev/null
+++ b/src/System.Web.Mvc/ReaderWriterCache`2.cs
@@ -0,0 +1,66 @@
+using System.Collections.Generic;
+using System.Diagnostics.CodeAnalysis;
+using System.Threading;
+
+namespace System.Web.Mvc
+{
+ [SuppressMessage("Microsoft.Design", "CA1001:TypesThatOwnDisposableFieldsShouldBeDisposable", Justification = "Instances of this type are meant to be singletons.")]
+ internal abstract class ReaderWriterCache<TKey, TValue>
+ {
+ private readonly Dictionary<TKey, TValue> _cache;
+ private readonly ReaderWriterLockSlim _readerWriterLock = new ReaderWriterLockSlim();
+
+ protected ReaderWriterCache()
+ : this(null)
+ {
+ }
+
+ protected ReaderWriterCache(IEqualityComparer<TKey> comparer)
+ {
+ _cache = new Dictionary<TKey, TValue>(comparer);
+ }
+
+ protected Dictionary<TKey, TValue> Cache
+ {
+ get { return _cache; }
+ }
+
+ protected TValue FetchOrCreateItem(TKey key, Func<TValue> creator)
+ {
+ // first, see if the item already exists in the cache
+ _readerWriterLock.EnterReadLock();
+ try
+ {
+ TValue existingEntry;
+ if (_cache.TryGetValue(key, out existingEntry))
+ {
+ return existingEntry;
+ }
+ }
+ finally
+ {
+ _readerWriterLock.ExitReadLock();
+ }
+
+ // insert the new item into the cache
+ TValue newEntry = creator();
+ _readerWriterLock.EnterWriteLock();
+ try
+ {
+ TValue existingEntry;
+ if (_cache.TryGetValue(key, out existingEntry))
+ {
+ // another thread already inserted an item, so use that one
+ return existingEntry;
+ }
+
+ _cache[key] = newEntry;
+ return newEntry;
+ }
+ finally
+ {
+ _readerWriterLock.ExitWriteLock();
+ }
+ }
+ }
+}
diff --git a/src/System.Web.Mvc/RedirectResult.cs b/src/System.Web.Mvc/RedirectResult.cs
new file mode 100644
index 00000000..0dfad042
--- /dev/null
+++ b/src/System.Web.Mvc/RedirectResult.cs
@@ -0,0 +1,56 @@
+using System.Diagnostics.CodeAnalysis;
+using System.Web.Mvc.Properties;
+
+namespace System.Web.Mvc
+{
+ // represents a result that performs a redirection given some URI
+ public class RedirectResult : ActionResult
+ {
+ [SuppressMessage("Microsoft.Design", "CA1054:UriParametersShouldNotBeStrings", MessageId = "0#", Justification = "Response.Redirect() takes its URI as a string parameter.")]
+ public RedirectResult(string url)
+ : this(url, permanent: false)
+ {
+ }
+
+ [SuppressMessage("Microsoft.Design", "CA1054:UriParametersShouldNotBeStrings", MessageId = "0#", Justification = "Response.Redirect() takes its URI as a string parameter.")]
+ public RedirectResult(string url, bool permanent)
+ {
+ if (String.IsNullOrEmpty(url))
+ {
+ throw new ArgumentException(MvcResources.Common_NullOrEmpty, "url");
+ }
+
+ Permanent = permanent;
+ Url = url;
+ }
+
+ public bool Permanent { get; private set; }
+
+ [SuppressMessage("Microsoft.Design", "CA1056:UriPropertiesShouldNotBeStrings", Justification = "Response.Redirect() takes its URI as a string parameter.")]
+ public string Url { get; private set; }
+
+ public override void ExecuteResult(ControllerContext context)
+ {
+ if (context == null)
+ {
+ throw new ArgumentNullException("context");
+ }
+ if (context.IsChildAction)
+ {
+ throw new InvalidOperationException(MvcResources.RedirectAction_CannotRedirectInChildAction);
+ }
+
+ string destinationUrl = UrlHelper.GenerateContentUrl(Url, context.HttpContext);
+ context.Controller.TempData.Keep();
+
+ if (Permanent)
+ {
+ context.HttpContext.Response.RedirectPermanent(destinationUrl, endResponse: false);
+ }
+ else
+ {
+ context.HttpContext.Response.Redirect(destinationUrl, endResponse: false);
+ }
+ }
+ }
+}
diff --git a/src/System.Web.Mvc/RedirectToRouteResult.cs b/src/System.Web.Mvc/RedirectToRouteResult.cs
new file mode 100644
index 00000000..c35846d0
--- /dev/null
+++ b/src/System.Web.Mvc/RedirectToRouteResult.cs
@@ -0,0 +1,77 @@
+using System.Web.Mvc.Properties;
+using System.Web.Routing;
+
+namespace System.Web.Mvc
+{
+ // represents a result that performs a redirection given some values dictionary
+ public class RedirectToRouteResult : ActionResult
+ {
+ private RouteCollection _routes;
+
+ public RedirectToRouteResult(RouteValueDictionary routeValues)
+ :
+ this(null, routeValues)
+ {
+ }
+
+ public RedirectToRouteResult(string routeName, RouteValueDictionary routeValues)
+ : this(routeName, routeValues, permanent: false)
+ {
+ }
+
+ public RedirectToRouteResult(string routeName, RouteValueDictionary routeValues, bool permanent)
+ {
+ Permanent = permanent;
+ RouteName = routeName ?? String.Empty;
+ RouteValues = routeValues ?? new RouteValueDictionary();
+ }
+
+ public bool Permanent { get; private set; }
+
+ public string RouteName { get; private set; }
+
+ public RouteValueDictionary RouteValues { get; private set; }
+
+ internal RouteCollection Routes
+ {
+ get
+ {
+ if (_routes == null)
+ {
+ _routes = RouteTable.Routes;
+ }
+ return _routes;
+ }
+ set { _routes = value; }
+ }
+
+ public override void ExecuteResult(ControllerContext context)
+ {
+ if (context == null)
+ {
+ throw new ArgumentNullException("context");
+ }
+ if (context.IsChildAction)
+ {
+ throw new InvalidOperationException(MvcResources.RedirectAction_CannotRedirectInChildAction);
+ }
+
+ string destinationUrl = UrlHelper.GenerateUrl(RouteName, null /* actionName */, null /* controllerName */, RouteValues, Routes, context.RequestContext, false /* includeImplicitMvcValues */);
+ if (String.IsNullOrEmpty(destinationUrl))
+ {
+ throw new InvalidOperationException(MvcResources.Common_NoRouteMatched);
+ }
+
+ context.Controller.TempData.Keep();
+
+ if (Permanent)
+ {
+ context.HttpContext.Response.RedirectPermanent(destinationUrl, endResponse: false);
+ }
+ else
+ {
+ context.HttpContext.Response.Redirect(destinationUrl, endResponse: false);
+ }
+ }
+ }
+}
diff --git a/src/System.Web.Mvc/ReflectedActionDescriptor.cs b/src/System.Web.Mvc/ReflectedActionDescriptor.cs
new file mode 100644
index 00000000..707521ec
--- /dev/null
+++ b/src/System.Web.Mvc/ReflectedActionDescriptor.cs
@@ -0,0 +1,135 @@
+using System.Collections.Generic;
+using System.Linq;
+using System.Reflection;
+using System.Web.Mvc.Properties;
+
+namespace System.Web.Mvc
+{
+ public class ReflectedActionDescriptor : ActionDescriptor
+ {
+ private readonly string _actionName;
+ private readonly ControllerDescriptor _controllerDescriptor;
+ private readonly Lazy<string> _uniqueId;
+ private ParameterDescriptor[] _parametersCache;
+
+ public ReflectedActionDescriptor(MethodInfo methodInfo, string actionName, ControllerDescriptor controllerDescriptor)
+ : this(methodInfo, actionName, controllerDescriptor, true /* validateMethod */)
+ {
+ }
+
+ internal ReflectedActionDescriptor(MethodInfo methodInfo, string actionName, ControllerDescriptor controllerDescriptor, bool validateMethod)
+ {
+ if (methodInfo == null)
+ {
+ throw new ArgumentNullException("methodInfo");
+ }
+ if (String.IsNullOrEmpty(actionName))
+ {
+ throw new ArgumentException(MvcResources.Common_NullOrEmpty, "actionName");
+ }
+ if (controllerDescriptor == null)
+ {
+ throw new ArgumentNullException("controllerDescriptor");
+ }
+
+ if (validateMethod)
+ {
+ string failedMessage = VerifyActionMethodIsCallable(methodInfo);
+ if (failedMessage != null)
+ {
+ throw new ArgumentException(failedMessage, "methodInfo");
+ }
+ }
+
+ MethodInfo = methodInfo;
+ _actionName = actionName;
+ _controllerDescriptor = controllerDescriptor;
+ _uniqueId = new Lazy<string>(CreateUniqueId);
+ }
+
+ public override string ActionName
+ {
+ get { return _actionName; }
+ }
+
+ public override ControllerDescriptor ControllerDescriptor
+ {
+ get { return _controllerDescriptor; }
+ }
+
+ public MethodInfo MethodInfo { get; private set; }
+
+ public override string UniqueId
+ {
+ get { return _uniqueId.Value; }
+ }
+
+ private string CreateUniqueId()
+ {
+ return base.UniqueId + DescriptorUtil.CreateUniqueId(MethodInfo);
+ }
+
+ public override object Execute(ControllerContext controllerContext, IDictionary<string, object> parameters)
+ {
+ if (controllerContext == null)
+ {
+ throw new ArgumentNullException("controllerContext");
+ }
+ if (parameters == null)
+ {
+ throw new ArgumentNullException("parameters");
+ }
+
+ ParameterInfo[] parameterInfos = MethodInfo.GetParameters();
+ var rawParameterValues = from parameterInfo in parameterInfos
+ select ExtractParameterFromDictionary(parameterInfo, parameters, MethodInfo);
+ object[] parametersArray = rawParameterValues.ToArray();
+
+ ActionMethodDispatcher dispatcher = DispatcherCache.GetDispatcher(MethodInfo);
+ object actionReturnValue = dispatcher.Execute(controllerContext.Controller, parametersArray);
+ return actionReturnValue;
+ }
+
+ public override object[] GetCustomAttributes(bool inherit)
+ {
+ return ActionDescriptorHelper.GetCustomAttributes(MethodInfo, inherit);
+ }
+
+ public override object[] GetCustomAttributes(Type attributeType, bool inherit)
+ {
+ return ActionDescriptorHelper.GetCustomAttributes(MethodInfo, attributeType, inherit);
+ }
+
+ public override IEnumerable<FilterAttribute> GetFilterAttributes(bool useCache)
+ {
+ if (useCache && GetType() == typeof(ReflectedActionDescriptor))
+ {
+ // Do not look at cache in types derived from this type because they might incorrectly implement GetCustomAttributes
+ return ReflectedAttributeCache.GetMethodFilterAttributes(MethodInfo);
+ }
+ return base.GetFilterAttributes(useCache);
+ }
+
+ public override ParameterDescriptor[] GetParameters()
+ {
+ return ActionDescriptorHelper.GetParameters(this, MethodInfo, ref _parametersCache);
+ }
+
+ public override ICollection<ActionSelector> GetSelectors()
+ {
+ return ActionDescriptorHelper.GetSelectors(MethodInfo);
+ }
+
+ public override bool IsDefined(Type attributeType, bool inherit)
+ {
+ return ActionDescriptorHelper.IsDefined(MethodInfo, attributeType, inherit);
+ }
+
+ internal static ReflectedActionDescriptor TryCreateDescriptor(MethodInfo methodInfo, string name, ControllerDescriptor controllerDescriptor)
+ {
+ ReflectedActionDescriptor descriptor = new ReflectedActionDescriptor(methodInfo, name, controllerDescriptor, false /* validateMethod */);
+ string failedMessage = VerifyActionMethodIsCallable(methodInfo);
+ return (failedMessage == null) ? descriptor : null;
+ }
+ }
+}
diff --git a/src/System.Web.Mvc/ReflectedAttributeCache.cs b/src/System.Web.Mvc/ReflectedAttributeCache.cs
new file mode 100644
index 00000000..98b3b159
--- /dev/null
+++ b/src/System.Web.Mvc/ReflectedAttributeCache.cs
@@ -0,0 +1,46 @@
+using System.Collections.Concurrent;
+using System.Collections.Generic;
+using System.Collections.ObjectModel;
+using System.Diagnostics;
+using System.Reflection;
+
+namespace System.Web.Mvc
+{
+ internal static class ReflectedAttributeCache
+ {
+ private static readonly ConcurrentDictionary<MethodInfo, ReadOnlyCollection<ActionMethodSelectorAttribute>> _actionMethodSelectorAttributeCache = new ConcurrentDictionary<MethodInfo, ReadOnlyCollection<ActionMethodSelectorAttribute>>();
+ private static readonly ConcurrentDictionary<MethodInfo, ReadOnlyCollection<ActionNameSelectorAttribute>> _actionNameSelectorAttributeCache = new ConcurrentDictionary<MethodInfo, ReadOnlyCollection<ActionNameSelectorAttribute>>();
+ private static readonly ConcurrentDictionary<MethodInfo, ReadOnlyCollection<FilterAttribute>> _methodFilterAttributeCache = new ConcurrentDictionary<MethodInfo, ReadOnlyCollection<FilterAttribute>>();
+
+ private static readonly ConcurrentDictionary<Type, ReadOnlyCollection<FilterAttribute>> _typeFilterAttributeCache = new ConcurrentDictionary<Type, ReadOnlyCollection<FilterAttribute>>();
+
+ public static ICollection<FilterAttribute> GetTypeFilterAttributes(Type type)
+ {
+ return GetAttributes(_typeFilterAttributeCache, type);
+ }
+
+ public static ICollection<FilterAttribute> GetMethodFilterAttributes(MethodInfo methodInfo)
+ {
+ return GetAttributes(_methodFilterAttributeCache, methodInfo);
+ }
+
+ public static ICollection<ActionMethodSelectorAttribute> GetActionMethodSelectorAttributes(MethodInfo methodInfo)
+ {
+ return GetAttributes(_actionMethodSelectorAttributeCache, methodInfo);
+ }
+
+ public static ICollection<ActionNameSelectorAttribute> GetActionNameSelectorAttributes(MethodInfo methodInfo)
+ {
+ return GetAttributes(_actionNameSelectorAttributeCache, methodInfo);
+ }
+
+ private static ReadOnlyCollection<TAttribute> GetAttributes<TMemberInfo, TAttribute>(ConcurrentDictionary<TMemberInfo, ReadOnlyCollection<TAttribute>> lookup, TMemberInfo memberInfo)
+ where TAttribute : Attribute
+ where TMemberInfo : MemberInfo
+ {
+ Debug.Assert(memberInfo != null);
+ Debug.Assert(lookup != null);
+ return lookup.GetOrAdd(memberInfo, mi => new ReadOnlyCollection<TAttribute>((TAttribute[])memberInfo.GetCustomAttributes(typeof(TAttribute), inherit: true)));
+ }
+ }
+}
diff --git a/src/System.Web.Mvc/ReflectedControllerDescriptor.cs b/src/System.Web.Mvc/ReflectedControllerDescriptor.cs
new file mode 100644
index 00000000..bdda9050
--- /dev/null
+++ b/src/System.Web.Mvc/ReflectedControllerDescriptor.cs
@@ -0,0 +1,99 @@
+using System.Collections.Generic;
+using System.Linq;
+using System.Reflection;
+using System.Web.Mvc.Properties;
+
+namespace System.Web.Mvc
+{
+ public class ReflectedControllerDescriptor : ControllerDescriptor
+ {
+ private readonly Type _controllerType;
+ private readonly ActionMethodSelector _selector;
+ private ActionDescriptor[] _canonicalActionsCache;
+
+ public ReflectedControllerDescriptor(Type controllerType)
+ {
+ if (controllerType == null)
+ {
+ throw new ArgumentNullException("controllerType");
+ }
+
+ _controllerType = controllerType;
+ _selector = new ActionMethodSelector(_controllerType);
+ }
+
+ public sealed override Type ControllerType
+ {
+ get { return _controllerType; }
+ }
+
+ public override ActionDescriptor FindAction(ControllerContext controllerContext, string actionName)
+ {
+ if (controllerContext == null)
+ {
+ throw new ArgumentNullException("controllerContext");
+ }
+ if (String.IsNullOrEmpty(actionName))
+ {
+ throw new ArgumentException(MvcResources.Common_NullOrEmpty, "actionName");
+ }
+
+ MethodInfo matched = _selector.FindActionMethod(controllerContext, actionName);
+ if (matched == null)
+ {
+ return null;
+ }
+
+ return new ReflectedActionDescriptor(matched, actionName, this);
+ }
+
+ private MethodInfo[] GetAllActionMethodsFromSelector()
+ {
+ List<MethodInfo> allValidMethods = new List<MethodInfo>();
+ allValidMethods.AddRange(_selector.AliasedMethods);
+ allValidMethods.AddRange(_selector.NonAliasedMethods.SelectMany(g => g));
+ return allValidMethods.ToArray();
+ }
+
+ public override ActionDescriptor[] GetCanonicalActions()
+ {
+ ActionDescriptor[] actions = LazilyFetchCanonicalActionsCollection();
+
+ // need to clone array so that user modifications aren't accidentally stored
+ return (ActionDescriptor[])actions.Clone();
+ }
+
+ public override object[] GetCustomAttributes(bool inherit)
+ {
+ return ControllerType.GetCustomAttributes(inherit);
+ }
+
+ public override object[] GetCustomAttributes(Type attributeType, bool inherit)
+ {
+ return ControllerType.GetCustomAttributes(attributeType, inherit);
+ }
+
+ public override IEnumerable<FilterAttribute> GetFilterAttributes(bool useCache)
+ {
+ if (useCache && GetType() == typeof(ReflectedControllerDescriptor))
+ {
+ // Do not look at cache in types derived from this type because they might incorrectly implement GetCustomAttributes
+ return ReflectedAttributeCache.GetTypeFilterAttributes(ControllerType);
+ }
+ return base.GetFilterAttributes(useCache);
+ }
+
+ public override bool IsDefined(Type attributeType, bool inherit)
+ {
+ return ControllerType.IsDefined(attributeType, inherit);
+ }
+
+ private ActionDescriptor[] LazilyFetchCanonicalActionsCollection()
+ {
+ return DescriptorUtil.LazilyFetchOrCreateDescriptors<MethodInfo, ActionDescriptor>(
+ ref _canonicalActionsCache /* cacheLocation */,
+ GetAllActionMethodsFromSelector /* initializer */,
+ methodInfo => ReflectedActionDescriptor.TryCreateDescriptor(methodInfo, methodInfo.Name, this) /* converter */);
+ }
+ }
+}
diff --git a/src/System.Web.Mvc/ReflectedParameterBindingInfo.cs b/src/System.Web.Mvc/ReflectedParameterBindingInfo.cs
new file mode 100644
index 00000000..27254f2d
--- /dev/null
+++ b/src/System.Web.Mvc/ReflectedParameterBindingInfo.cs
@@ -0,0 +1,62 @@
+using System.Collections.Generic;
+using System.Collections.ObjectModel;
+using System.Globalization;
+using System.Reflection;
+using System.Web.Mvc.Properties;
+
+namespace System.Web.Mvc
+{
+ internal class ReflectedParameterBindingInfo : ParameterBindingInfo
+ {
+ private readonly ParameterInfo _parameterInfo;
+ private ICollection<string> _exclude = new string[0];
+ private ICollection<string> _include = new string[0];
+ private string _prefix;
+
+ public ReflectedParameterBindingInfo(ParameterInfo parameterInfo)
+ {
+ _parameterInfo = parameterInfo;
+ ReadSettingsFromBindAttribute();
+ }
+
+ public override IModelBinder Binder
+ {
+ get
+ {
+ IModelBinder binder = ModelBinders.GetBinderFromAttributes(_parameterInfo,
+ () => String.Format(CultureInfo.CurrentCulture, MvcResources.ReflectedParameterBindingInfo_MultipleConverterAttributes,
+ _parameterInfo.Name, _parameterInfo.Member));
+
+ return binder;
+ }
+ }
+
+ public override ICollection<string> Exclude
+ {
+ get { return _exclude; }
+ }
+
+ public override ICollection<string> Include
+ {
+ get { return _include; }
+ }
+
+ public override string Prefix
+ {
+ get { return _prefix; }
+ }
+
+ private void ReadSettingsFromBindAttribute()
+ {
+ BindAttribute attr = (BindAttribute)Attribute.GetCustomAttribute(_parameterInfo, typeof(BindAttribute));
+ if (attr == null)
+ {
+ return;
+ }
+
+ _exclude = new ReadOnlyCollection<string>(AuthorizeAttribute.SplitString(attr.Exclude));
+ _include = new ReadOnlyCollection<string>(AuthorizeAttribute.SplitString(attr.Include));
+ _prefix = attr.Prefix;
+ }
+ }
+}
diff --git a/src/System.Web.Mvc/ReflectedParameterDescriptor.cs b/src/System.Web.Mvc/ReflectedParameterDescriptor.cs
new file mode 100644
index 00000000..28f653df
--- /dev/null
+++ b/src/System.Web.Mvc/ReflectedParameterDescriptor.cs
@@ -0,0 +1,79 @@
+using System.Reflection;
+
+namespace System.Web.Mvc
+{
+ public class ReflectedParameterDescriptor : ParameterDescriptor
+ {
+ private readonly ActionDescriptor _actionDescriptor;
+ private readonly ReflectedParameterBindingInfo _bindingInfo;
+
+ public ReflectedParameterDescriptor(ParameterInfo parameterInfo, ActionDescriptor actionDescriptor)
+ {
+ if (parameterInfo == null)
+ {
+ throw new ArgumentNullException("parameterInfo");
+ }
+ if (actionDescriptor == null)
+ {
+ throw new ArgumentNullException("actionDescriptor");
+ }
+
+ ParameterInfo = parameterInfo;
+ _actionDescriptor = actionDescriptor;
+ _bindingInfo = new ReflectedParameterBindingInfo(parameterInfo);
+ }
+
+ public override ActionDescriptor ActionDescriptor
+ {
+ get { return _actionDescriptor; }
+ }
+
+ public override ParameterBindingInfo BindingInfo
+ {
+ get { return _bindingInfo; }
+ }
+
+ public override object DefaultValue
+ {
+ get
+ {
+ object value;
+ if (ParameterInfoUtil.TryGetDefaultValue(ParameterInfo, out value))
+ {
+ return value;
+ }
+ else
+ {
+ return base.DefaultValue;
+ }
+ }
+ }
+
+ public ParameterInfo ParameterInfo { get; private set; }
+
+ public override string ParameterName
+ {
+ get { return ParameterInfo.Name; }
+ }
+
+ public override Type ParameterType
+ {
+ get { return ParameterInfo.ParameterType; }
+ }
+
+ public override object[] GetCustomAttributes(bool inherit)
+ {
+ return ParameterInfo.GetCustomAttributes(inherit);
+ }
+
+ public override object[] GetCustomAttributes(Type attributeType, bool inherit)
+ {
+ return ParameterInfo.GetCustomAttributes(attributeType, inherit);
+ }
+
+ public override bool IsDefined(Type attributeType, bool inherit)
+ {
+ return ParameterInfo.IsDefined(attributeType, inherit);
+ }
+ }
+}
diff --git a/src/System.Web.Mvc/RegularExpressionAttributeAdapter.cs b/src/System.Web.Mvc/RegularExpressionAttributeAdapter.cs
new file mode 100644
index 00000000..18617497
--- /dev/null
+++ b/src/System.Web.Mvc/RegularExpressionAttributeAdapter.cs
@@ -0,0 +1,18 @@
+using System.Collections.Generic;
+using System.ComponentModel.DataAnnotations;
+
+namespace System.Web.Mvc
+{
+ public class RegularExpressionAttributeAdapter : DataAnnotationsModelValidator<RegularExpressionAttribute>
+ {
+ public RegularExpressionAttributeAdapter(ModelMetadata metadata, ControllerContext context, RegularExpressionAttribute attribute)
+ : base(metadata, context, attribute)
+ {
+ }
+
+ public override IEnumerable<ModelClientValidationRule> GetClientValidationRules()
+ {
+ return new[] { new ModelClientValidationRegexRule(ErrorMessage, Attribute.Pattern) };
+ }
+ }
+}
diff --git a/src/System.Web.Mvc/RemoteAttribute.cs b/src/System.Web.Mvc/RemoteAttribute.cs
new file mode 100644
index 00000000..68230a46
--- /dev/null
+++ b/src/System.Web.Mvc/RemoteAttribute.cs
@@ -0,0 +1,139 @@
+using System.Collections.Generic;
+using System.ComponentModel.DataAnnotations;
+using System.Diagnostics.CodeAnalysis;
+using System.Globalization;
+using System.Web.Mvc.Properties;
+using System.Web.Routing;
+
+namespace System.Web.Mvc
+{
+ [AttributeUsage(AttributeTargets.Property)]
+ [SuppressMessage("Microsoft.Design", "CA1019:DefineAccessorsForAttributeArguments", Justification = "The constructor parameters are used to feed RouteData, which is public.")]
+ [SuppressMessage("Microsoft.Performance", "CA1813:AvoidUnsealedAttributes", Justification = "This attribute is designed to be a base class for other attributes.")]
+ public class RemoteAttribute : ValidationAttribute, IClientValidatable
+ {
+ private string _additionalFields;
+ private string[] _additonalFieldsSplit = new string[0];
+
+ protected RemoteAttribute()
+ : base(MvcResources.RemoteAttribute_RemoteValidationFailed)
+ {
+ RouteData = new RouteValueDictionary();
+ }
+
+ public RemoteAttribute(string routeName)
+ : this()
+ {
+ if (String.IsNullOrWhiteSpace(routeName))
+ {
+ throw new ArgumentException(MvcResources.Common_NullOrEmpty, "routeName");
+ }
+
+ RouteName = routeName;
+ }
+
+ public RemoteAttribute(string action, string controller)
+ :
+ this(action, controller, null /* areaName */)
+ {
+ }
+
+ public RemoteAttribute(string action, string controller, string areaName)
+ : this()
+ {
+ if (String.IsNullOrWhiteSpace(action))
+ {
+ throw new ArgumentException(MvcResources.Common_NullOrEmpty, "action");
+ }
+ if (String.IsNullOrWhiteSpace(controller))
+ {
+ throw new ArgumentException(MvcResources.Common_NullOrEmpty, "controller");
+ }
+
+ RouteData["controller"] = controller;
+ RouteData["action"] = action;
+
+ if (!String.IsNullOrWhiteSpace(areaName))
+ {
+ RouteData["area"] = areaName;
+ }
+ }
+
+ public string HttpMethod { get; set; }
+
+ public string AdditionalFields
+ {
+ get { return _additionalFields ?? String.Empty; }
+ set
+ {
+ _additionalFields = value;
+ _additonalFieldsSplit = AuthorizeAttribute.SplitString(value);
+ }
+ }
+
+ protected RouteValueDictionary RouteData { get; private set; }
+
+ protected string RouteName { get; set; }
+
+ protected virtual RouteCollection Routes
+ {
+ get { return RouteTable.Routes; }
+ }
+
+ public string FormatAdditionalFieldsForClientValidation(string property)
+ {
+ if (String.IsNullOrEmpty(property))
+ {
+ throw new ArgumentException(MvcResources.Common_NullOrEmpty, "property");
+ }
+
+ string delimitedAdditionalFields = FormatPropertyForClientValidation(property);
+
+ foreach (string field in _additonalFieldsSplit)
+ {
+ delimitedAdditionalFields += "," + FormatPropertyForClientValidation(field);
+ }
+
+ return delimitedAdditionalFields;
+ }
+
+ public static string FormatPropertyForClientValidation(string property)
+ {
+ if (String.IsNullOrEmpty(property))
+ {
+ throw new ArgumentException(MvcResources.Common_NullOrEmpty, "property");
+ }
+ return "*." + property;
+ }
+
+ [SuppressMessage("Microsoft.Design", "CA1055:UriReturnValuesShouldNotBeStrings", Justification = "The value is a not a regular URL since it may contain ~/ ASP.NET-specific characters")]
+ protected virtual string GetUrl(ControllerContext controllerContext)
+ {
+ var pathData = Routes.GetVirtualPathForArea(controllerContext.RequestContext,
+ RouteName,
+ RouteData);
+
+ if (pathData == null)
+ {
+ throw new InvalidOperationException(MvcResources.RemoteAttribute_NoUrlFound);
+ }
+
+ return pathData.VirtualPath;
+ }
+
+ public override string FormatErrorMessage(string name)
+ {
+ return String.Format(CultureInfo.CurrentCulture, ErrorMessageString, name);
+ }
+
+ public override bool IsValid(object value)
+ {
+ return true;
+ }
+
+ public IEnumerable<ModelClientValidationRule> GetClientValidationRules(ModelMetadata metadata, ControllerContext context)
+ {
+ yield return new ModelClientValidationRemoteRule(FormatErrorMessage(metadata.GetDisplayName()), GetUrl(context), HttpMethod, FormatAdditionalFieldsForClientValidation(metadata.PropertyName));
+ }
+ }
+}
diff --git a/src/System.Web.Mvc/RequireHttpsAttribute.cs b/src/System.Web.Mvc/RequireHttpsAttribute.cs
new file mode 100644
index 00000000..f39b7865
--- /dev/null
+++ b/src/System.Web.Mvc/RequireHttpsAttribute.cs
@@ -0,0 +1,38 @@
+using System.Diagnostics.CodeAnalysis;
+using System.Web.Mvc.Properties;
+
+namespace System.Web.Mvc
+{
+ [SuppressMessage("Microsoft.Performance", "CA1813:AvoidUnsealedAttributes", Justification = "Unsealed because type contains virtual extensibility points.")]
+ [AttributeUsage(AttributeTargets.Class | AttributeTargets.Method, Inherited = true, AllowMultiple = false)]
+ public class RequireHttpsAttribute : FilterAttribute, IAuthorizationFilter
+ {
+ public virtual void OnAuthorization(AuthorizationContext filterContext)
+ {
+ if (filterContext == null)
+ {
+ throw new ArgumentNullException("filterContext");
+ }
+
+ if (!filterContext.HttpContext.Request.IsSecureConnection)
+ {
+ HandleNonHttpsRequest(filterContext);
+ }
+ }
+
+ protected virtual void HandleNonHttpsRequest(AuthorizationContext filterContext)
+ {
+ // only redirect for GET requests, otherwise the browser might not propagate the verb and request
+ // body correctly.
+
+ if (!String.Equals(filterContext.HttpContext.Request.HttpMethod, "GET", StringComparison.OrdinalIgnoreCase))
+ {
+ throw new InvalidOperationException(MvcResources.RequireHttpsAttribute_MustUseSsl);
+ }
+
+ // redirect to HTTPS version of page
+ string url = "https://" + filterContext.HttpContext.Request.Url.Host + filterContext.HttpContext.Request.RawUrl;
+ filterContext.Result = new RedirectResult(url);
+ }
+ }
+}
diff --git a/src/System.Web.Mvc/RequiredAttributeAdapter.cs b/src/System.Web.Mvc/RequiredAttributeAdapter.cs
new file mode 100644
index 00000000..bfdce5fc
--- /dev/null
+++ b/src/System.Web.Mvc/RequiredAttributeAdapter.cs
@@ -0,0 +1,18 @@
+using System.Collections.Generic;
+using System.ComponentModel.DataAnnotations;
+
+namespace System.Web.Mvc
+{
+ public class RequiredAttributeAdapter : DataAnnotationsModelValidator<RequiredAttribute>
+ {
+ public RequiredAttributeAdapter(ModelMetadata metadata, ControllerContext context, RequiredAttribute attribute)
+ : base(metadata, context, attribute)
+ {
+ }
+
+ public override IEnumerable<ModelClientValidationRule> GetClientValidationRules()
+ {
+ return new[] { new ModelClientValidationRequiredRule(ErrorMessage) };
+ }
+ }
+}
diff --git a/src/System.Web.Mvc/ResultExecutedContext.cs b/src/System.Web.Mvc/ResultExecutedContext.cs
new file mode 100644
index 00000000..c5e70127
--- /dev/null
+++ b/src/System.Web.Mvc/ResultExecutedContext.cs
@@ -0,0 +1,34 @@
+using System.Diagnostics.CodeAnalysis;
+
+namespace System.Web.Mvc
+{
+ public class ResultExecutedContext : ControllerContext
+ {
+ // parameterless constructor used for mocking
+ public ResultExecutedContext()
+ {
+ }
+
+ [SuppressMessage("Microsoft.Usage", "CA2214:DoNotCallOverridableMethodsInConstructors", Justification = "The virtual property setters are only to support mocking frameworks, in which case this constructor shouldn't be called anyway.")]
+ public ResultExecutedContext(ControllerContext controllerContext, ActionResult result, bool canceled, Exception exception)
+ : base(controllerContext)
+ {
+ if (result == null)
+ {
+ throw new ArgumentNullException("result");
+ }
+
+ Result = result;
+ Canceled = canceled;
+ Exception = exception;
+ }
+
+ public virtual bool Canceled { get; set; }
+
+ public virtual Exception Exception { get; set; }
+
+ public bool ExceptionHandled { get; set; }
+
+ public virtual ActionResult Result { get; set; }
+ }
+}
diff --git a/src/System.Web.Mvc/ResultExecutingContext.cs b/src/System.Web.Mvc/ResultExecutingContext.cs
new file mode 100644
index 00000000..8d1910db
--- /dev/null
+++ b/src/System.Web.Mvc/ResultExecutingContext.cs
@@ -0,0 +1,28 @@
+using System.Diagnostics.CodeAnalysis;
+
+namespace System.Web.Mvc
+{
+ public class ResultExecutingContext : ControllerContext
+ {
+ // parameterless constructor used for mocking
+ public ResultExecutingContext()
+ {
+ }
+
+ [SuppressMessage("Microsoft.Usage", "CA2214:DoNotCallOverridableMethodsInConstructors", Justification = "The virtual property setters are only to support mocking frameworks, in which case this constructor shouldn't be called anyway.")]
+ public ResultExecutingContext(ControllerContext controllerContext, ActionResult result)
+ : base(controllerContext)
+ {
+ if (result == null)
+ {
+ throw new ArgumentNullException("result");
+ }
+
+ Result = result;
+ }
+
+ public bool Cancel { get; set; }
+
+ public virtual ActionResult Result { get; set; }
+ }
+}
diff --git a/src/System.Web.Mvc/RouteCollectionExtensions.cs b/src/System.Web.Mvc/RouteCollectionExtensions.cs
new file mode 100644
index 00000000..95626bbc
--- /dev/null
+++ b/src/System.Web.Mvc/RouteCollectionExtensions.cs
@@ -0,0 +1,193 @@
+using System.Diagnostics.CodeAnalysis;
+using System.Web.Routing;
+
+namespace System.Web.Mvc
+{
+ public static class RouteCollectionExtensions
+ {
+ // This method returns a new RouteCollection containing only routes that matched a particular area.
+ // The Boolean out parameter is just a flag specifying whether any registered routes were area-aware.
+ private static RouteCollection FilterRouteCollectionByArea(RouteCollection routes, string areaName, out bool usingAreas)
+ {
+ if (areaName == null)
+ {
+ areaName = String.Empty;
+ }
+
+ usingAreas = false;
+ RouteCollection filteredRoutes = new RouteCollection();
+
+ using (routes.GetReadLock())
+ {
+ foreach (RouteBase route in routes)
+ {
+ string thisAreaName = AreaHelpers.GetAreaName(route) ?? String.Empty;
+ usingAreas |= (thisAreaName.Length > 0);
+ if (String.Equals(thisAreaName, areaName, StringComparison.OrdinalIgnoreCase))
+ {
+ filteredRoutes.Add(route);
+ }
+ }
+ }
+
+ // if areas are not in use, the filtered route collection might be incorrect
+ return (usingAreas) ? filteredRoutes : routes;
+ }
+
+ public static VirtualPathData GetVirtualPathForArea(this RouteCollection routes, RequestContext requestContext, RouteValueDictionary values)
+ {
+ return GetVirtualPathForArea(routes, requestContext, null /* name */, values);
+ }
+
+ public static VirtualPathData GetVirtualPathForArea(this RouteCollection routes, RequestContext requestContext, string name, RouteValueDictionary values)
+ {
+ bool usingAreas; // don't care about this value
+ return GetVirtualPathForArea(routes, requestContext, name, values, out usingAreas);
+ }
+
+ internal static VirtualPathData GetVirtualPathForArea(this RouteCollection routes, RequestContext requestContext, string name, RouteValueDictionary values, out bool usingAreas)
+ {
+ if (routes == null)
+ {
+ throw new ArgumentNullException("routes");
+ }
+
+ if (!String.IsNullOrEmpty(name))
+ {
+ // the route name is a stronger qualifier than the area name, so just pipe it through
+ usingAreas = false;
+ return routes.GetVirtualPath(requestContext, name, values);
+ }
+
+ string targetArea = null;
+ if (values != null)
+ {
+ object targetAreaRawValue;
+ if (values.TryGetValue("area", out targetAreaRawValue))
+ {
+ targetArea = targetAreaRawValue as string;
+ }
+ else
+ {
+ // set target area to current area
+ if (requestContext != null)
+ {
+ targetArea = AreaHelpers.GetAreaName(requestContext.RouteData);
+ }
+ }
+ }
+
+ // need to apply a correction to the RVD if areas are in use
+ RouteValueDictionary correctedValues = values;
+ RouteCollection filteredRoutes = FilterRouteCollectionByArea(routes, targetArea, out usingAreas);
+ if (usingAreas)
+ {
+ correctedValues = new RouteValueDictionary(values);
+ correctedValues.Remove("area");
+ }
+
+ VirtualPathData vpd = filteredRoutes.GetVirtualPath(requestContext, correctedValues);
+ return vpd;
+ }
+
+ [SuppressMessage("Microsoft.Design", "CA1054:UriParametersShouldNotBeStrings", MessageId = "1#", Justification = "This is not a regular URL as it may contain special routing characters.")]
+ public static void IgnoreRoute(this RouteCollection routes, string url)
+ {
+ IgnoreRoute(routes, url, null /* constraints */);
+ }
+
+ [SuppressMessage("Microsoft.Design", "CA1054:UriParametersShouldNotBeStrings", MessageId = "1#", Justification = "This is not a regular URL as it may contain special routing characters.")]
+ public static void IgnoreRoute(this RouteCollection routes, string url, object constraints)
+ {
+ if (routes == null)
+ {
+ throw new ArgumentNullException("routes");
+ }
+ if (url == null)
+ {
+ throw new ArgumentNullException("url");
+ }
+
+ IgnoreRouteInternal route = new IgnoreRouteInternal(url)
+ {
+ Constraints = new RouteValueDictionary(constraints)
+ };
+
+ routes.Add(route);
+ }
+
+ [SuppressMessage("Microsoft.Design", "CA1054:UriParametersShouldNotBeStrings", MessageId = "2#", Justification = "This is not a regular URL as it may contain special routing characters.")]
+ public static Route MapRoute(this RouteCollection routes, string name, string url)
+ {
+ return MapRoute(routes, name, url, null /* defaults */, (object)null /* constraints */);
+ }
+
+ [SuppressMessage("Microsoft.Design", "CA1054:UriParametersShouldNotBeStrings", MessageId = "2#", Justification = "This is not a regular URL as it may contain special routing characters.")]
+ public static Route MapRoute(this RouteCollection routes, string name, string url, object defaults)
+ {
+ return MapRoute(routes, name, url, defaults, (object)null /* constraints */);
+ }
+
+ [SuppressMessage("Microsoft.Design", "CA1054:UriParametersShouldNotBeStrings", MessageId = "2#", Justification = "This is not a regular URL as it may contain special routing characters.")]
+ public static Route MapRoute(this RouteCollection routes, string name, string url, object defaults, object constraints)
+ {
+ return MapRoute(routes, name, url, defaults, constraints, null /* namespaces */);
+ }
+
+ [SuppressMessage("Microsoft.Design", "CA1054:UriParametersShouldNotBeStrings", MessageId = "2#", Justification = "This is not a regular URL as it may contain special routing characters.")]
+ public static Route MapRoute(this RouteCollection routes, string name, string url, string[] namespaces)
+ {
+ return MapRoute(routes, name, url, null /* defaults */, null /* constraints */, namespaces);
+ }
+
+ [SuppressMessage("Microsoft.Design", "CA1054:UriParametersShouldNotBeStrings", MessageId = "2#", Justification = "This is not a regular URL as it may contain special routing characters.")]
+ public static Route MapRoute(this RouteCollection routes, string name, string url, object defaults, string[] namespaces)
+ {
+ return MapRoute(routes, name, url, defaults, null /* constraints */, namespaces);
+ }
+
+ [SuppressMessage("Microsoft.Design", "CA1054:UriParametersShouldNotBeStrings", MessageId = "2#", Justification = "This is not a regular URL as it may contain special routing characters.")]
+ public static Route MapRoute(this RouteCollection routes, string name, string url, object defaults, object constraints, string[] namespaces)
+ {
+ if (routes == null)
+ {
+ throw new ArgumentNullException("routes");
+ }
+ if (url == null)
+ {
+ throw new ArgumentNullException("url");
+ }
+
+ Route route = new Route(url, new MvcRouteHandler())
+ {
+ Defaults = new RouteValueDictionary(defaults),
+ Constraints = new RouteValueDictionary(constraints),
+ DataTokens = new RouteValueDictionary()
+ };
+
+ if ((namespaces != null) && (namespaces.Length > 0))
+ {
+ route.DataTokens["Namespaces"] = namespaces;
+ }
+
+ routes.Add(name, route);
+
+ return route;
+ }
+
+ private sealed class IgnoreRouteInternal : Route
+ {
+ public IgnoreRouteInternal(string url)
+ : base(url, new StopRoutingHandler())
+ {
+ }
+
+ public override VirtualPathData GetVirtualPath(RequestContext requestContext, RouteValueDictionary routeValues)
+ {
+ // Never match during route generation. This avoids the scenario where an IgnoreRoute with
+ // fairly relaxed constraints ends up eagerly matching all generated URLs.
+ return null;
+ }
+ }
+ }
+}
diff --git a/src/System.Web.Mvc/RouteDataValueProvider.cs b/src/System.Web.Mvc/RouteDataValueProvider.cs
new file mode 100644
index 00000000..f31ec75a
--- /dev/null
+++ b/src/System.Web.Mvc/RouteDataValueProvider.cs
@@ -0,0 +1,14 @@
+using System.Globalization;
+
+namespace System.Web.Mvc
+{
+ public sealed class RouteDataValueProvider : DictionaryValueProvider<object>
+ {
+ // RouteData should use the invariant culture since it's part of the URL, and the URL should be
+ // interpreted in a uniform fashion regardless of the origin of a particular request.
+ public RouteDataValueProvider(ControllerContext controllerContext)
+ : base(controllerContext.RouteData.Values, CultureInfo.InvariantCulture)
+ {
+ }
+ }
+}
diff --git a/src/System.Web.Mvc/RouteDataValueProviderFactory.cs b/src/System.Web.Mvc/RouteDataValueProviderFactory.cs
new file mode 100644
index 00000000..b2054158
--- /dev/null
+++ b/src/System.Web.Mvc/RouteDataValueProviderFactory.cs
@@ -0,0 +1,15 @@
+namespace System.Web.Mvc
+{
+ public sealed class RouteDataValueProviderFactory : ValueProviderFactory
+ {
+ public override IValueProvider GetValueProvider(ControllerContext controllerContext)
+ {
+ if (controllerContext == null)
+ {
+ throw new ArgumentNullException("controllerContext");
+ }
+
+ return new RouteDataValueProvider(controllerContext);
+ }
+ }
+}
diff --git a/src/System.Web.Mvc/RouteValuesHelpers.cs b/src/System.Web.Mvc/RouteValuesHelpers.cs
new file mode 100644
index 00000000..4e4b6904
--- /dev/null
+++ b/src/System.Web.Mvc/RouteValuesHelpers.cs
@@ -0,0 +1,60 @@
+using System.Collections.Generic;
+using System.Web.Routing;
+
+namespace System.Web.Mvc
+{
+ internal static class RouteValuesHelpers
+ {
+ public static RouteValueDictionary GetRouteValues(RouteValueDictionary routeValues)
+ {
+ return (routeValues != null) ? new RouteValueDictionary(routeValues) : new RouteValueDictionary();
+ }
+
+ public static RouteValueDictionary MergeRouteValues(string actionName, string controllerName, RouteValueDictionary implicitRouteValues, RouteValueDictionary routeValues, bool includeImplicitMvcValues)
+ {
+ // Create a new dictionary containing implicit and auto-generated values
+ RouteValueDictionary mergedRouteValues = new RouteValueDictionary();
+
+ if (includeImplicitMvcValues)
+ {
+ // We only include MVC-specific values like 'controller' and 'action' if we are generating an action link.
+ // If we are generating a route link [as to MapRoute("Foo", "any/url", new { controller = ... })], including
+ // the current controller name will cause the route match to fail if the current controller is not the same
+ // as the destination controller.
+
+ object implicitValue;
+ if (implicitRouteValues != null && implicitRouteValues.TryGetValue("action", out implicitValue))
+ {
+ mergedRouteValues["action"] = implicitValue;
+ }
+
+ if (implicitRouteValues != null && implicitRouteValues.TryGetValue("controller", out implicitValue))
+ {
+ mergedRouteValues["controller"] = implicitValue;
+ }
+ }
+
+ // Merge values from the user's dictionary/object
+ if (routeValues != null)
+ {
+ foreach (KeyValuePair<string, object> routeElement in GetRouteValues(routeValues))
+ {
+ mergedRouteValues[routeElement.Key] = routeElement.Value;
+ }
+ }
+
+ // Merge explicit parameters when not null
+ if (actionName != null)
+ {
+ mergedRouteValues["action"] = actionName;
+ }
+
+ if (controllerName != null)
+ {
+ mergedRouteValues["controller"] = controllerName;
+ }
+
+ return mergedRouteValues;
+ }
+ }
+}
diff --git a/src/System.Web.Mvc/SecurityUtil.cs b/src/System.Web.Mvc/SecurityUtil.cs
new file mode 100644
index 00000000..becded41
--- /dev/null
+++ b/src/System.Web.Mvc/SecurityUtil.cs
@@ -0,0 +1,79 @@
+using System.Diagnostics.CodeAnalysis;
+using System.Reflection;
+using System.Security;
+
+namespace System.Web.Mvc
+{
+ internal static class SecurityUtil
+ {
+ private static Action<Action> _callInAppTrustThunk;
+
+ // !! IMPORTANT !!
+ // Do not try to optimize this method or perform any extra caching; doing so could lead to MVC not operating
+ // correctly until the AppDomain is restarted.
+ [SuppressMessage("Microsoft.Security", "CA2107:ReviewDenyAndPermitOnlyUsage",
+ Justification = "This is essentially the same logic as Page.ProcessRequest.")]
+ [SuppressMessage("Microsoft.Design", "CA1031:DoNotCatchGeneralExceptionTypes",
+ Justification = "If an exception is thrown, assume we're running in same trust level as the application itself, so we don't need to do anything special.")]
+ private static Action<Action> GetCallInAppTrustThunk()
+ {
+ // do we need to create the thunk?
+ if (_callInAppTrustThunk == null)
+ {
+ try
+ {
+ if (!typeof(SecurityUtil).Assembly.IsFullyTrusted /* bin-deployed */
+ || AppDomain.CurrentDomain.IsHomogenous /* .NET 4 CAS model */)
+ {
+ // we're already running in the application's trust level, so nothing to do
+ _callInAppTrustThunk = f => f();
+ }
+ else
+ {
+ // legacy CAS model - need to lower own permission level to be compatible with legacy systems
+ // This is essentially the same logic as Page.ProcessRequest(HttpContext)
+ NamedPermissionSet namedPermissionSet = (NamedPermissionSet)typeof(HttpRuntime).GetProperty("NamedPermissionSet", BindingFlags.NonPublic | BindingFlags.Public | BindingFlags.Static).GetValue(null, null);
+ bool disableProcessRequestInApplicationTrust = (bool)typeof(HttpRuntime).GetProperty("DisableProcessRequestInApplicationTrust", BindingFlags.NonPublic | BindingFlags.Public | BindingFlags.Static).GetValue(null, null);
+ if (namedPermissionSet != null && !disableProcessRequestInApplicationTrust)
+ {
+ _callInAppTrustThunk = f =>
+ {
+ // lower permissions
+ namedPermissionSet.PermitOnly();
+ f();
+ };
+ }
+ else
+ {
+ // application's trust level is FullTrust, so nothing to do
+ _callInAppTrustThunk = f => f();
+ }
+ }
+ }
+ catch
+ {
+ // MVC assembly is already running in application trust, so swallow exceptions
+ }
+ }
+
+ // if there was an error, just process transparently
+ return _callInAppTrustThunk ?? (Action<Action>)(f => f());
+ }
+
+ public static TResult ProcessInApplicationTrust<TResult>(Func<TResult> func)
+ {
+ TResult result = default(TResult);
+ ProcessInApplicationTrust(delegate
+ {
+ result = func();
+ });
+ return result;
+ }
+
+ public static void ProcessInApplicationTrust(Action action)
+ {
+ Action<Action> executor = GetCallInAppTrustThunk();
+ executor(action);
+ }
+ }
+}
diff --git a/src/System.Web.Mvc/SelectList.cs b/src/System.Web.Mvc/SelectList.cs
new file mode 100644
index 00000000..2c605386
--- /dev/null
+++ b/src/System.Web.Mvc/SelectList.cs
@@ -0,0 +1,37 @@
+using System.Collections;
+using System.Diagnostics.CodeAnalysis;
+
+namespace System.Web.Mvc
+{
+ [SuppressMessage("Microsoft.Naming", "CA1710:IdentifiersShouldHaveCorrectSuffix", Justification = "This is a shipped API")]
+ public class SelectList : MultiSelectList
+ {
+ public SelectList(IEnumerable items)
+ : this(items, null /* selectedValue */)
+ {
+ }
+
+ public SelectList(IEnumerable items, object selectedValue)
+ : this(items, null /* dataValuefield */, null /* dataTextField */, selectedValue)
+ {
+ }
+
+ public SelectList(IEnumerable items, string dataValueField, string dataTextField)
+ : this(items, dataValueField, dataTextField, null /* selectedValue */)
+ {
+ }
+
+ public SelectList(IEnumerable items, string dataValueField, string dataTextField, object selectedValue)
+ : base(items, dataValueField, dataTextField, ToEnumerable(selectedValue))
+ {
+ SelectedValue = selectedValue;
+ }
+
+ public object SelectedValue { get; private set; }
+
+ private static IEnumerable ToEnumerable(object selectedValue)
+ {
+ return (selectedValue != null) ? new object[] { selectedValue } : null;
+ }
+ }
+}
diff --git a/src/System.Web.Mvc/SelectListItem.cs b/src/System.Web.Mvc/SelectListItem.cs
new file mode 100644
index 00000000..650540d9
--- /dev/null
+++ b/src/System.Web.Mvc/SelectListItem.cs
@@ -0,0 +1,11 @@
+namespace System.Web.Mvc
+{
+ public class SelectListItem
+ {
+ public bool Selected { get; set; }
+
+ public string Text { get; set; }
+
+ public string Value { get; set; }
+ }
+}
diff --git a/src/System.Web.Mvc/SessionStateAttribute.cs b/src/System.Web.Mvc/SessionStateAttribute.cs
new file mode 100644
index 00000000..c675bf60
--- /dev/null
+++ b/src/System.Web.Mvc/SessionStateAttribute.cs
@@ -0,0 +1,15 @@
+using System.Web.SessionState;
+
+namespace System.Web.Mvc
+{
+ [AttributeUsage(AttributeTargets.Class, AllowMultiple = false, Inherited = true)]
+ public sealed class SessionStateAttribute : Attribute
+ {
+ public SessionStateAttribute(SessionStateBehavior behavior)
+ {
+ Behavior = behavior;
+ }
+
+ public SessionStateBehavior Behavior { get; private set; }
+ }
+}
diff --git a/src/System.Web.Mvc/SessionStateTempDataProvider.cs b/src/System.Web.Mvc/SessionStateTempDataProvider.cs
new file mode 100644
index 00000000..01f83f08
--- /dev/null
+++ b/src/System.Web.Mvc/SessionStateTempDataProvider.cs
@@ -0,0 +1,64 @@
+using System.Collections.Generic;
+using System.Web.Mvc.Properties;
+
+namespace System.Web.Mvc
+{
+ public class SessionStateTempDataProvider : ITempDataProvider
+ {
+ internal const string TempDataSessionStateKey = "__ControllerTempData";
+
+ public virtual IDictionary<string, object> LoadTempData(ControllerContext controllerContext)
+ {
+ HttpSessionStateBase session = controllerContext.HttpContext.Session;
+
+ if (session != null)
+ {
+ Dictionary<string, object> tempDataDictionary = session[TempDataSessionStateKey] as Dictionary<string, object>;
+
+ if (tempDataDictionary != null)
+ {
+ // If we got it from Session, remove it so that no other request gets it
+ session.Remove(TempDataSessionStateKey);
+ return tempDataDictionary;
+ }
+ }
+
+ return new Dictionary<string, object>(StringComparer.OrdinalIgnoreCase);
+ }
+
+ public virtual void SaveTempData(ControllerContext controllerContext, IDictionary<string, object> values)
+ {
+ if (controllerContext == null)
+ {
+ throw new ArgumentNullException("controllerContext");
+ }
+
+ HttpSessionStateBase session = controllerContext.HttpContext.Session;
+ bool isDirty = (values != null && values.Count > 0);
+
+ if (session == null)
+ {
+ if (isDirty)
+ {
+ throw new InvalidOperationException(MvcResources.SessionStateTempDataProvider_SessionStateDisabled);
+ }
+ }
+ else
+ {
+ if (isDirty)
+ {
+ session[TempDataSessionStateKey] = values;
+ }
+ else
+ {
+ // Since the default implementation of Remove() (from SessionStateItemCollection) dirties the
+ // collection, we shouldn't call it unless we really do need to remove the existing key.
+ if (session[TempDataSessionStateKey] != null)
+ {
+ session.Remove(TempDataSessionStateKey);
+ }
+ }
+ }
+ }
+ }
+}
diff --git a/src/System.Web.Mvc/SingleServiceResolver.cs b/src/System.Web.Mvc/SingleServiceResolver.cs
new file mode 100644
index 00000000..b1f5c344
--- /dev/null
+++ b/src/System.Web.Mvc/SingleServiceResolver.cs
@@ -0,0 +1,65 @@
+using System.Globalization;
+using System.Web.Mvc.Properties;
+
+namespace System.Web.Mvc
+{
+ internal class SingleServiceResolver<TService> : IResolver<TService>
+ where TService : class
+ {
+ private TService _currentValueFromResolver;
+ private Func<TService> _currentValueThunk;
+ private TService _defaultValue;
+ private Func<IDependencyResolver> _resolverThunk;
+ private string _callerMethodName;
+
+ public SingleServiceResolver(Func<TService> currentValueThunk, TService defaultValue, string callerMethodName)
+ {
+ if (currentValueThunk == null)
+ {
+ throw new ArgumentNullException("currentValueThunk");
+ }
+ if (defaultValue == null)
+ {
+ throw new ArgumentNullException("defaultValue");
+ }
+
+ _resolverThunk = () => DependencyResolver.Current;
+ _currentValueThunk = currentValueThunk;
+ _defaultValue = defaultValue;
+ _callerMethodName = callerMethodName;
+ }
+
+ internal SingleServiceResolver(Func<TService> staticAccessor, TService defaultValue, IDependencyResolver resolver, string callerMethodName)
+ : this(staticAccessor, defaultValue, callerMethodName)
+ {
+ if (resolver != null)
+ {
+ _resolverThunk = () => resolver;
+ }
+ }
+
+ public TService Current
+ {
+ get
+ {
+ if (_resolverThunk != null)
+ {
+ lock (_currentValueThunk)
+ {
+ if (_resolverThunk != null)
+ {
+ _currentValueFromResolver = _resolverThunk().GetService<TService>();
+ _resolverThunk = null;
+
+ if (_currentValueFromResolver != null && _currentValueThunk() != null)
+ {
+ throw new InvalidOperationException(String.Format(CultureInfo.CurrentCulture, MvcResources.SingleServiceResolver_CannotRegisterTwoInstances, typeof(TService).Name.ToString(), _callerMethodName));
+ }
+ }
+ }
+ }
+ return _currentValueFromResolver ?? _currentValueThunk() ?? _defaultValue;
+ }
+ }
+ }
+}
diff --git a/src/System.Web.Mvc/StringLengthAttributeAdapter.cs b/src/System.Web.Mvc/StringLengthAttributeAdapter.cs
new file mode 100644
index 00000000..ad301038
--- /dev/null
+++ b/src/System.Web.Mvc/StringLengthAttributeAdapter.cs
@@ -0,0 +1,18 @@
+using System.Collections.Generic;
+using System.ComponentModel.DataAnnotations;
+
+namespace System.Web.Mvc
+{
+ public class StringLengthAttributeAdapter : DataAnnotationsModelValidator<StringLengthAttribute>
+ {
+ public StringLengthAttributeAdapter(ModelMetadata metadata, ControllerContext context, StringLengthAttribute attribute)
+ : base(metadata, context, attribute)
+ {
+ }
+
+ public override IEnumerable<ModelClientValidationRule> GetClientValidationRules()
+ {
+ return new[] { new ModelClientValidationStringLengthRule(ErrorMessage, Attribute.MinimumLength, Attribute.MaximumLength) };
+ }
+ }
+}
diff --git a/src/System.Web.Mvc/System.Web.Mvc.csproj b/src/System.Web.Mvc/System.Web.Mvc.csproj
new file mode 100644
index 00000000..646cce61
--- /dev/null
+++ b/src/System.Web.Mvc/System.Web.Mvc.csproj
@@ -0,0 +1,475 @@
+<?xml version="1.0" encoding="utf-8"?>
+<Project ToolsVersion="4.0" DefaultTargets="Build" xmlns="http://schemas.microsoft.com/developer/msbuild/2003">
+ <Import Project="$([MSBuild]::GetDirectoryNameOfFileAbove($(MSBuildThisFileDirectory),Runtime.sln))\tools\WebStack.settings.targets" />
+ <PropertyGroup>
+ <Configuration Condition=" '$(Configuration)' == '' ">Debug</Configuration>
+ <Platform Condition=" '$(Platform)' == '' ">AnyCPU</Platform>
+ <CodeAnalysis Condition=" '$(CodeAnalysis)' == '' ">false</CodeAnalysis>
+ <ProductVersion>9.0.30729</ProductVersion>
+ <SchemaVersion>2.0</SchemaVersion>
+ <ProjectGuid>{3D3FFD8A-624D-4E9B-954B-E1C105507975}</ProjectGuid>
+ <OutputType>Library</OutputType>
+ <AppDesignerFolder>Properties</AppDesignerFolder>
+ <RootNamespace>System.Web.Mvc</RootNamespace>
+ <AssemblyName>System.Web.Mvc</AssemblyName>
+ <FileAlignment>512</FileAlignment>
+ <ErrorReport>prompt</ErrorReport>
+ <WarningLevel>4</WarningLevel>
+ <TreatWarningsAsErrors>true</TreatWarningsAsErrors>
+ <BaseAddress>1609891840</BaseAddress>
+ </PropertyGroup>
+ <PropertyGroup Condition=" '$(Configuration)|$(Platform)' == 'Debug|AnyCPU' ">
+ <DebugSymbols>true</DebugSymbols>
+ <DebugType>full</DebugType>
+ <Optimize>false</Optimize>
+ <OutputPath>..\..\bin\Debug\</OutputPath>
+ <DefineConstants>TRACE;DEBUG;ASPNETMVC</DefineConstants>
+ <CodeAnalysisRuleSet>..\Strict.ruleset</CodeAnalysisRuleSet>
+ <DocumentationFile>$(OutputPath)\$(AssemblyName).xml</DocumentationFile>
+ <NoWarn>1591</NoWarn>
+ </PropertyGroup>
+ <PropertyGroup Condition=" '$(Configuration)|$(Platform)' == 'Release|AnyCPU' ">
+ <DebugType>pdbonly</DebugType>
+ <Optimize>true</Optimize>
+ <OutputPath>..\..\bin\Release\</OutputPath>
+ <DefineConstants>TRACE;ASPNETMVC</DefineConstants>
+ <CodeAnalysisRuleSet>..\Strict.ruleset</CodeAnalysisRuleSet>
+ <RunCodeAnalysis>$(CodeAnalysis)</RunCodeAnalysis>
+ <DocumentationFile>$(OutputPath)\$(AssemblyName).xml</DocumentationFile>
+ <NoWarn>1591</NoWarn>
+ </PropertyGroup>
+ <PropertyGroup Condition="'$(Configuration)|$(Platform)' == 'CodeCoverage|AnyCPU'">
+ <DebugSymbols>true</DebugSymbols>
+ <OutputPath>..\..\bin\CodeCoverage\</OutputPath>
+ <DefineConstants>TRACE;DEBUG;CODE_COVERAGE;ASPNETMVC</DefineConstants>
+ <DebugType>full</DebugType>
+ <CodeAnalysisRuleSet>..\Strict.ruleset</CodeAnalysisRuleSet>
+ </PropertyGroup>
+ <ItemGroup>
+ <Reference Include="Microsoft.Web.Infrastructure, Version=1.0.0.0, Culture=neutral, PublicKeyToken=31bf3856ad364e35, processorArchitecture=MSIL">
+ <HintPath>..\..\packages\Microsoft.Web.Infrastructure.1.0.0.0\lib\net40\Microsoft.Web.Infrastructure.dll</HintPath>
+ </Reference>
+ <Reference Include="System" />
+ <Reference Include="System.ComponentModel.DataAnnotations" />
+ <Reference Include="System.configuration" />
+ <Reference Include="System.Core" />
+ <Reference Include="System.Data.Entity" />
+ <Reference Include="System.Data.Linq" />
+ <Reference Include="System.Runtime.Caching" />
+ <Reference Include="System.Web" />
+ <Reference Include="System.Data" />
+ <Reference Include="System.Web.Abstractions" />
+ <Reference Include="System.Web.Extensions" />
+ <Reference Include="System.Web.Routing" />
+ <Reference Include="System.Xml" />
+ </ItemGroup>
+ <ItemGroup>
+ <Compile Include="..\CommonAssemblyInfo.cs">
+ <Link>Properties\CommonAssemblyInfo.cs</Link>
+ </Compile>
+ <Compile Include="..\System.Web.Http.Common\TaskHelpers.cs">
+ <Link>Async\TaskHelpers.cs</Link>
+ </Compile>
+ <Compile Include="..\System.Web.Http.Common\TaskHelpersExtensions.cs">
+ <Link>Async\TaskHelpersExtensions.cs</Link>
+ </Compile>
+ <Compile Include="..\TransparentCommonAssemblyInfo.cs">
+ <Link>Properties\TransparentCommonAssemblyInfo.cs</Link>
+ </Compile>
+ <Compile Include="AdditionalMetaDataAttribute.cs" />
+ <Compile Include="AllowAnonymousAttribute.cs" />
+ <Compile Include="ActionDescriptorHelper.cs" />
+ <Compile Include="Async\TaskAsyncActionDescriptor.cs" />
+ <Compile Include="Async\TaskWrapperAsyncResult.cs" />
+ <Compile Include="BuildManagerCompiledView.cs" />
+ <Compile Include="BuildManagerViewEngine.cs" />
+ <Compile Include="CachedAssociatedMetadataProvider`1.cs" />
+ <Compile Include="CachedDataAnnotationsMetadataAttributes.cs" />
+ <Compile Include="CachedDataAnnotationsModelMetadata.cs" />
+ <Compile Include="CachedDataAnnotationsModelMetadataProvider.cs" />
+ <Compile Include="CachedModelMetadata`1.cs" />
+ <Compile Include="CancellationTokenModelBinder.cs" />
+ <Compile Include="CompareAttribute.cs" />
+ <Compile Include="ChildActionValueProvider.cs" />
+ <Compile Include="ChildActionValueProviderFactory.cs" />
+ <Compile Include="IEnumerableValueProvider.cs" />
+ <Compile Include="DataTypeUtil.cs" />
+ <Compile Include="Html\DisplayNameExtensions.cs" />
+ <Compile Include="Html\NameExtensions.cs" />
+ <Compile Include="Html\ValueExtensions.cs" />
+ <Compile Include="Razor\MvcCSharpRazorCodeGenerator.cs" />
+ <Compile Include="Razor\SetModelTypeCodeGenerator.cs" />
+ <Compile Include="ReflectedAttributeCache.cs" />
+ <Compile Include="SessionStateAttribute.cs" />
+ <Compile Include="AllowHtmlAttribute.cs" />
+ <Compile Include="UnvalidatedRequestValuesAccessor.cs" />
+ <Compile Include="UnvalidatedRequestValuesWrapper.cs" />
+ <Compile Include="IUnvalidatedRequestValues.cs" />
+ <Compile Include="IUnvalidatedValueProvider.cs" />
+ <Compile Include="DependencyResolverExtensions.cs" />
+ <Compile Include="ExpressionUtil\BinaryExpressionFingerprint.cs" />
+ <Compile Include="ExpressionUtil\CachedExpressionCompiler.cs" />
+ <Compile Include="ExpressionUtil\ConditionalExpressionFingerprint.cs" />
+ <Compile Include="ExpressionUtil\ConstantExpressionFingerprint.cs" />
+ <Compile Include="ExpressionUtil\DefaultExpressionFingerprint.cs" />
+ <Compile Include="ExpressionUtil\ExpressionFingerprint.cs" />
+ <Compile Include="ExpressionUtil\ExpressionFingerprintChain.cs" />
+ <Compile Include="ExpressionUtil\FingerprintingExpressionVisitor.cs" />
+ <Compile Include="ExpressionUtil\HashCodeCombiner.cs" />
+ <Compile Include="ExpressionUtil\Hoisted`2.cs" />
+ <Compile Include="ExpressionUtil\HoistingExpressionVisitor.cs" />
+ <Compile Include="ExpressionUtil\IndexExpressionFingerprint.cs" />
+ <Compile Include="ExpressionUtil\LambdaExpressionFingerprint.cs" />
+ <Compile Include="ExpressionUtil\MemberExpressionFingerprint.cs" />
+ <Compile Include="ExpressionUtil\MethodCallExpressionFingerprint.cs" />
+ <Compile Include="ExpressionUtil\ParameterExpressionFingerprint.cs" />
+ <Compile Include="ExpressionUtil\TypeBinaryExpressionFingerprint.cs" />
+ <Compile Include="ExpressionUtil\UnaryExpressionFingerprint.cs" />
+ <Compile Include="IControllerActivator.cs" />
+ <Compile Include="IModelBinderProvider.cs" />
+ <Compile Include="IUniquelyIdentifiable.cs" />
+ <Compile Include="IViewStartPageChild.cs" />
+ <Compile Include="IResolver.cs" />
+ <Compile Include="ControllerInstanceFilterProvider.cs" />
+ <Compile Include="RazorView.cs" />
+ <Compile Include="RazorViewEngine.cs" />
+ <Compile Include="DynamicViewDataDictionary.cs" />
+ <Compile Include="Filter.cs" />
+ <Compile Include="FilterAttributeFilterProvider.cs" />
+ <Compile Include="FilterProviderCollection.cs" />
+ <Compile Include="FilterProviders.cs" />
+ <Compile Include="FilterScope.cs" />
+ <Compile Include="GlobalFilterCollection.cs" />
+ <Compile Include="GlobalFilters.cs" />
+ <Compile Include="IFilterProvider.cs" />
+ <Compile Include="IMvcFilter.cs" />
+ <Compile Include="IViewPageActivator.cs" />
+ <Compile Include="ModelBinderProviderCollection.cs" />
+ <Compile Include="ModelBinderProviders.cs" />
+ <Compile Include="MultiServiceResolver.cs" />
+ <Compile Include="Razor\MvcCSharpRazorCodeParser.cs" />
+ <Compile Include="MvcFilter.cs" />
+ <Compile Include="Razor\MvcVBRazorCodeParser.cs" />
+ <Compile Include="Razor\MvcWebPageRazorHost.cs" />
+ <Compile Include="MvcWebRazorHostFactory.cs" />
+ <Compile Include="PreApplicationStartCode.cs" />
+ <Compile Include="RemoteAttribute.cs" />
+ <Compile Include="SecurityUtil.cs" />
+ <Compile Include="SingleServiceResolver.cs" />
+ <Compile Include="Razor\StartPageLookupDelegate.cs" />
+ <Compile Include="TagBuilderExtensions.cs" />
+ <Compile Include="UrlRewriterHelper.cs" />
+ <Compile Include="ViewStartPage.cs" />
+ <Compile Include="WebViewPage.cs" />
+ <Compile Include="WebViewPage`1.cs" />
+ <Compile Include="HttpNotFoundResult.cs" />
+ <Compile Include="HttpStatusCodeResult.cs" />
+ <Compile Include="IMvcControlBuilder.cs" />
+ <Compile Include="AssociatedMetadataProvider.cs" />
+ <Compile Include="ActionExecutedContext.cs" />
+ <Compile Include="ActionExecutingContext.cs" />
+ <Compile Include="ClientDataTypeModelValidatorProvider.cs" />
+ <Compile Include="AssociatedValidatorProvider.cs" />
+ <Compile Include="Async\ActionDescriptorCreator.cs" />
+ <Compile Include="Async\AsyncActionDescriptor.cs" />
+ <Compile Include="Async\AsyncActionMethodSelector.cs" />
+ <Compile Include="Async\AsyncControllerActionInvoker.cs" />
+ <Compile Include="Async\SynchronousOperationException.cs" />
+ <Compile Include="Async\AsyncManager.cs" />
+ <Compile Include="AsyncTimeoutAttribute.cs" />
+ <Compile Include="Async\BeginInvokeDelegate.cs" />
+ <Compile Include="Async\AsyncResultWrapper.cs" />
+ <Compile Include="Async\AsyncVoid.cs" />
+ <Compile Include="AsyncController.cs" />
+ <Compile Include="Async\AsyncUtil.cs" />
+ <Compile Include="Async\IAsyncController.cs" />
+ <Compile Include="Async\IAsyncActionInvoker.cs" />
+ <Compile Include="Async\IAsyncManagerContainer.cs" />
+ <Compile Include="IClientValidatable.cs" />
+ <Compile Include="IMetadataAware.cs" />
+ <Compile Include="IDependencyResolver.cs" />
+ <Compile Include="JsonValueProviderFactory.cs" />
+ <Compile Include="DependencyResolver.cs" />
+ <Compile Include="UrlParameter.cs" />
+ <Compile Include="FormValueProvider.cs" />
+ <Compile Include="FormValueProviderFactory.cs" />
+ <Compile Include="HttpFileCollectionValueProvider.cs" />
+ <Compile Include="HttpFileCollectionValueProviderFactory.cs" />
+ <Compile Include="QueryStringValueProvider.cs" />
+ <Compile Include="QueryStringValueProviderFactory.cs" />
+ <Compile Include="RangeAttributeAdapter.cs" />
+ <Compile Include="RegularExpressionAttributeAdapter.cs" />
+ <Compile Include="RequiredAttributeAdapter.cs" />
+ <Compile Include="RouteDataValueProvider.cs" />
+ <Compile Include="RouteDataValueProviderFactory.cs" />
+ <Compile Include="StringLengthAttributeAdapter.cs" />
+ <Compile Include="TypeCacheUtil.cs" />
+ <Compile Include="TypeCacheSerializer.cs" />
+ <Compile Include="Html\DisplayTextExtensions.cs" />
+ <Compile Include="NoAsyncTimeoutAttribute.cs" />
+ <Compile Include="Async\OperationCounter.cs" />
+ <Compile Include="Async\ReflectedAsyncActionDescriptor.cs" />
+ <Compile Include="Async\ReflectedAsyncControllerDescriptor.cs" />
+ <Compile Include="Async\Trigger.cs" />
+ <Compile Include="Async\TriggerListener.cs" />
+ <Compile Include="Async\SimpleAsyncResult.cs" />
+ <Compile Include="Async\EndInvokeDelegate.cs" />
+ <Compile Include="Async\EndInvokeDelegate`1.cs" />
+ <Compile Include="Async\SynchronizationContextUtil.cs" />
+ <Compile Include="AuthorizationContext.cs" />
+ <Compile Include="ByteArrayModelBinder.cs" />
+ <Compile Include="ControllerContext.cs" />
+ <Compile Include="Html\ChildActionExtensions.cs" />
+ <Compile Include="ParameterInfoUtil.cs" />
+ <Compile Include="HttpHandlerUtil.cs" />
+ <Compile Include="ChildActionOnlyAttribute.cs" />
+ <Compile Include="TypeDescriptorHelper.cs" />
+ <Compile Include="ValidatableObjectAdapter.cs" />
+ <Compile Include="ValueProviderFactories.cs" />
+ <Compile Include="ValueProviderFactory.cs" />
+ <Compile Include="ValueProviderFactoryCollection.cs" />
+ <Compile Include="ValueProviderCollection.cs" />
+ <Compile Include="DictionaryValueProvider`1.cs" />
+ <Compile Include="NameValueCollectionValueProvider.cs" />
+ <Compile Include="ValueProviderUtil.cs" />
+ <Compile Include="IValueProvider.cs" />
+ <Compile Include="DataErrorInfoModelValidatorProvider.cs" />
+ <Compile Include="ModelValidatorProviderCollection.cs" />
+ <Compile Include="DataAnnotationsModelMetadata.cs" />
+ <Compile Include="HiddenInputAttribute.cs" />
+ <Compile Include="HttpGetAttribute.cs" />
+ <Compile Include="HttpPutAttribute.cs" />
+ <Compile Include="HttpDeleteAttribute.cs" />
+ <Compile Include="MvcHtmlString.cs" />
+ <Compile Include="DataAnnotationsModelValidator.cs" />
+ <Compile Include="DataAnnotationsModelValidatorProvider.cs" />
+ <Compile Include="DataAnnotationsModelValidator`1.cs" />
+ <Compile Include="EmptyModelValidatorProvider.cs" />
+ <Compile Include="ExpressionHelper.cs" />
+ <Compile Include="FieldValidationMetadata.cs" />
+ <Compile Include="FormContext.cs" />
+ <Compile Include="JsonRequestBehavior.cs" />
+ <Compile Include="ModelValidationResult.cs" />
+ <Compile Include="ModelValidator.cs" />
+ <Compile Include="ModelValidatorProvider.cs" />
+ <Compile Include="ModelValidatorProviders.cs" />
+ <Compile Include="RequireHttpsAttribute.cs" />
+ <Compile Include="HttpRequestExtensions.cs" />
+ <Compile Include="DataAnnotationsModelMetadataProvider.cs" />
+ <Compile Include="EmptyModelMetadataProvider.cs" />
+ <Compile Include="ModelMetadata.cs" />
+ <Compile Include="ModelMetadataProvider.cs" />
+ <Compile Include="ModelMetadataProviders.cs" />
+ <Compile Include="AreaHelpers.cs" />
+ <Compile Include="AreaRegistration.cs" />
+ <Compile Include="AreaRegistrationContext.cs" />
+ <Compile Include="Error.cs" />
+ <Compile Include="IRouteWithArea.cs" />
+ <Compile Include="Async\SingleEntryGate.cs" />
+ <Compile Include="Html\PartialExtensions.cs" />
+ <Compile Include="LinqBinaryModelBinder.cs" />
+ <Compile Include="TryGetValueDelegate.cs" />
+ <Compile Include="ViewDataInfo.cs" />
+ <Compile Include="Html\DefaultDisplayTemplates.cs" />
+ <Compile Include="Html\DefaultEditorTemplates.cs" />
+ <Compile Include="Html\DisplayExtensions.cs" />
+ <Compile Include="Html\EditorExtensions.cs" />
+ <Compile Include="Html\LabelExtensions.cs" />
+ <Compile Include="Html\TemplateHelpers.cs" />
+ <Compile Include="HttpPostAttribute.cs" />
+ <Compile Include="PathHelpers.cs" />
+ <Compile Include="ExceptionContext.cs" />
+ <Compile Include="ResultExecutedContext.cs" />
+ <Compile Include="ResultExecutingContext.cs" />
+ <Compile Include="TemplateInfo.cs" />
+ <Compile Include="ValidateAntiForgeryTokenAttribute.cs" />
+ <Compile Include="JavaScriptResult.cs" />
+ <Compile Include="ActionDescriptor.cs" />
+ <Compile Include="ActionMethodDispatcher.cs" />
+ <Compile Include="ActionMethodSelector.cs" />
+ <Compile Include="ActionMethodSelectorAttribute.cs" />
+ <Compile Include="ActionNameSelectorAttribute.cs" />
+ <Compile Include="AuthorizeAttribute.cs" />
+ <Compile Include="Ajax\AjaxOptions.cs" />
+ <Compile Include="Ajax\AjaxExtensions.cs" />
+ <Compile Include="ActionMethodDispatcherCache.cs" />
+ <Compile Include="BindAttribute.cs" />
+ <Compile Include="ControllerBase.cs" />
+ <Compile Include="ActionNameAttribute.cs" />
+ <Compile Include="AcceptVerbsAttribute.cs" />
+ <Compile Include="AjaxHelper`1.cs" />
+ <Compile Include="HtmlHelper`1.cs" />
+ <Compile Include="DictionaryHelpers.cs" />
+ <Compile Include="AjaxRequestExtensions.cs" />
+ <Compile Include="ModelBinderDictionary.cs" />
+ <Compile Include="ValueProviderDictionary.cs" />
+ <Compile Include="ViewContext.cs" />
+ <Compile Include="ViewMasterPageControlBuilder.cs" />
+ <Compile Include="ViewTemplateUserControl.cs">
+ <SubType>ASPXCodeBehind</SubType>
+ </Compile>
+ <Compile Include="ViewTemplateUserControl`1.cs">
+ <SubType>ASPXCodeBehind</SubType>
+ </Compile>
+ <Compile Include="ViewType.cs" />
+ <Compile Include="ViewTypeControlBuilder.cs" />
+ <Compile Include="ViewUserControlControlBuilder.cs" />
+ <Compile Include="ViewPageControlBuilder.cs" />
+ <Compile Include="ViewTypeParserFilter.cs" />
+ <Compile Include="DefaultViewLocationCache.cs" />
+ <Compile Include="FormCollection.cs" />
+ <Compile Include="HttpPostedFileBaseModelBinder.cs" />
+ <Compile Include="NullViewLocationCache.cs" />
+ <Compile Include="ValidateInputAttribute.cs" />
+ <Compile Include="FileContentResult.cs" />
+ <Compile Include="FilePathResult.cs" />
+ <Compile Include="FileResult.cs" />
+ <Compile Include="FileStreamResult.cs" />
+ <Compile Include="InputType.cs" />
+ <Compile Include="ControllerDescriptorCache.cs" />
+ <Compile Include="ReflectedParameterBindingInfo.cs" />
+ <Compile Include="ParameterBindingInfo.cs" />
+ <Compile Include="ReaderWriterCache`2.cs" />
+ <Compile Include="DescriptorUtil.cs" />
+ <Compile Include="ReflectedControllerDescriptor.cs" />
+ <Compile Include="ControllerDescriptor.cs" />
+ <Compile Include="ActionSelector.cs" />
+ <Compile Include="ReflectedActionDescriptor.cs" />
+ <Compile Include="Html\MvcForm.cs" />
+ <Compile Include="HttpVerbs.cs" />
+ <Compile Include="DefaultModelBinder.cs" />
+ <Compile Include="ModelBindingContext.cs" />
+ <Compile Include="ParameterDescriptor.cs" />
+ <Compile Include="RouteValuesHelpers.cs" />
+ <Compile Include="SelectListItem.cs" />
+ <Compile Include="ReflectedParameterDescriptor.cs" />
+ <Compile Include="ValueProviderResult.cs" />
+ <Compile Include="CustomModelBinderAttribute.cs" />
+ <Compile Include="FormMethod.cs" />
+ <Compile Include="Html\FormExtensions.cs" />
+ <Compile Include="Html\InputExtensions.cs" />
+ <Compile Include="Html\RenderPartialExtensions.cs" />
+ <Compile Include="Html\SelectExtensions.cs" />
+ <Compile Include="Html\TextAreaExtensions.cs" />
+ <Compile Include="Html\ValidationExtensions.cs" />
+ <Compile Include="IModelBinder.cs" />
+ <Compile Include="Html\LinkExtensions.cs" />
+ <Compile Include="ModelBinderAttribute.cs" />
+ <Compile Include="ModelBinders.cs" />
+ <Compile Include="ModelStateDictionary.cs" />
+ <Compile Include="ModelState.cs" />
+ <Compile Include="ModelErrorCollection.cs" />
+ <Compile Include="ModelError.cs" />
+ <Compile Include="Ajax\InsertionMode.cs" />
+ <Compile Include="HandleErrorAttribute.cs" />
+ <Compile Include="HandleErrorInfo.cs" />
+ <Compile Include="HttpUnauthorizedResult.cs" />
+ <Compile Include="IActionInvoker.cs" />
+ <Compile Include="IView.cs" />
+ <Compile Include="IViewLocationCache.cs" />
+ <Compile Include="MvcHttpHandler.cs" />
+ <Compile Include="PartialViewResult.cs" />
+ <Compile Include="SessionStateTempDataProvider.cs" />
+ <Compile Include="ITempDataProvider.cs" />
+ <Compile Include="OutputCacheAttribute.cs" />
+ <Compile Include="FilterInfo.cs" />
+ <Compile Include="GlobalSuppressions.cs" />
+ <Compile Include="ActionFilterAttribute.cs" />
+ <Compile Include="ActionResult.cs" />
+ <Compile Include="AjaxHelper.cs" />
+ <Compile Include="BuildManagerWrapper.cs" />
+ <Compile Include="Controller.cs" />
+ <Compile Include="ControllerActionInvoker.cs" />
+ <Compile Include="ControllerBuilder.cs" />
+ <Compile Include="ControllerTypeCache.cs" />
+ <Compile Include="ContentResult.cs" />
+ <Compile Include="FilterAttribute.cs" />
+ <Compile Include="IResultFilter.cs" />
+ <Compile Include="IExceptionFilter.cs" />
+ <Compile Include="IAuthorizationFilter.cs" />
+ <Compile Include="JsonResult.cs" />
+ <Compile Include="NameValueCollectionExtensions.cs" />
+ <Compile Include="ViewDataDictionary`1.cs" />
+ <Compile Include="EmptyResult.cs" />
+ <Compile Include="MultiSelectList.cs" />
+ <Compile Include="RedirectResult.cs" />
+ <Compile Include="RedirectToRouteResult.cs" />
+ <Compile Include="DefaultControllerFactory.cs" />
+ <Compile Include="HtmlHelper.cs" />
+ <Compile Include="IActionFilter.cs" />
+ <Compile Include="IBuildManager.cs" />
+ <Compile Include="IController.cs" />
+ <Compile Include="IControllerFactory.cs" />
+ <Compile Include="IViewDataContainer.cs" />
+ <Compile Include="IViewEngine.cs" />
+ <Compile Include="MvcHandler.cs" />
+ <Compile Include="MvcRouteHandler.cs" />
+ <Compile Include="NonActionAttribute.cs" />
+ <Compile Include="RouteCollectionExtensions.cs" />
+ <Compile Include="SelectList.cs" />
+ <Compile Include="TempDataDictionary.cs" />
+ <Compile Include="TypeHelpers.cs" />
+ <Compile Include="UrlHelper.cs" />
+ <Compile Include="ViewDataDictionary.cs" />
+ <Compile Include="ViewEngineCollection.cs" />
+ <Compile Include="ViewEngineResult.cs" />
+ <Compile Include="ViewEngines.cs" />
+ <Compile Include="ViewMasterPage.cs">
+ <SubType>ASPXCodeBehind</SubType>
+ </Compile>
+ <Compile Include="ViewMasterPage`1.cs">
+ <SubType>ASPXCodeBehind</SubType>
+ </Compile>
+ <Compile Include="ViewPage.cs">
+ <SubType>ASPXCodeBehind</SubType>
+ </Compile>
+ <Compile Include="ViewPage`1.cs">
+ <SubType>ASPXCodeBehind</SubType>
+ </Compile>
+ <Compile Include="ViewResult.cs" />
+ <Compile Include="ViewResultBase.cs" />
+ <Compile Include="ViewUserControl.cs">
+ <SubType>ASPXCodeBehind</SubType>
+ </Compile>
+ <Compile Include="ViewUserControl`1.cs">
+ <SubType>ASPXCodeBehind</SubType>
+ </Compile>
+ <Compile Include="VirtualPathProviderViewEngine.cs" />
+ <Compile Include="WebFormView.cs" />
+ <Compile Include="WebFormViewEngine.cs" />
+ <Compile Include="Properties\AssemblyInfo.cs" />
+ <Compile Include="Properties\MvcResources.Designer.cs">
+ <AutoGen>True</AutoGen>
+ <DesignTime>True</DesignTime>
+ <DependentUpon>MvcResources.resx</DependentUpon>
+ </Compile>
+ </ItemGroup>
+ <ItemGroup>
+ <EmbeddedResource Include="Properties\MvcResources.resx">
+ <Generator>ResXFileCodeGenerator</Generator>
+ <LastGenOutput>MvcResources.Designer.cs</LastGenOutput>
+ <SubType>Designer</SubType>
+ </EmbeddedResource>
+ </ItemGroup>
+ <ItemGroup>
+ <CodeAnalysisDictionary Include="..\CodeAnalysisDictionary.xml" />
+ </ItemGroup>
+ <ItemGroup>
+ <ProjectReference Include="..\System.Web.Razor\System.Web.Razor.csproj">
+ <Project>{8F18041B-9410-4C36-A9C5-067813DF5F31}</Project>
+ <Name>System.Web.Razor</Name>
+ </ProjectReference>
+ <ProjectReference Include="..\System.Web.WebPages.Razor\System.Web.WebPages.Razor.csproj">
+ <Project>{0939B11A-FE4E-4BA1-8AD6-D97741EE314F}</Project>
+ <Name>System.Web.WebPages.Razor</Name>
+ </ProjectReference>
+ <ProjectReference Include="..\System.Web.WebPages\System.Web.WebPages.csproj">
+ <Project>{76EFA9C5-8D7E-4FDF-B710-E20F8B6B00D2}</Project>
+ <Name>System.Web.WebPages</Name>
+ </ProjectReference>
+ </ItemGroup>
+ <ItemGroup>
+ <None Include="packages.config" />
+ </ItemGroup>
+ <Import Project="$(MSBuildToolsPath)\Microsoft.CSharp.targets" />
+</Project> \ No newline at end of file
diff --git a/src/System.Web.Mvc/TagBuilderExtensions.cs b/src/System.Web.Mvc/TagBuilderExtensions.cs
new file mode 100644
index 00000000..cd458f59
--- /dev/null
+++ b/src/System.Web.Mvc/TagBuilderExtensions.cs
@@ -0,0 +1,13 @@
+using System.Diagnostics;
+
+namespace System.Web.Mvc
+{
+ internal static class TagBuilderExtensions
+ {
+ internal static MvcHtmlString ToMvcHtmlString(this TagBuilder tagBuilder, TagRenderMode renderMode)
+ {
+ Debug.Assert(tagBuilder != null);
+ return new MvcHtmlString(tagBuilder.ToString(renderMode));
+ }
+ }
+}
diff --git a/src/System.Web.Mvc/TempDataDictionary.cs b/src/System.Web.Mvc/TempDataDictionary.cs
new file mode 100644
index 00000000..76bb4b2c
--- /dev/null
+++ b/src/System.Web.Mvc/TempDataDictionary.cs
@@ -0,0 +1,208 @@
+using System.Collections;
+using System.Collections.Generic;
+using System.Linq;
+
+namespace System.Web.Mvc
+{
+ public class TempDataDictionary : IDictionary<string, object>
+ {
+ internal const string TempDataSerializationKey = "__tempData";
+
+ private Dictionary<string, object> _data;
+ private HashSet<string> _initialKeys = new HashSet<string>(StringComparer.OrdinalIgnoreCase);
+ private HashSet<string> _retainedKeys = new HashSet<string>(StringComparer.OrdinalIgnoreCase);
+
+ public TempDataDictionary()
+ {
+ _data = new Dictionary<string, object>(StringComparer.OrdinalIgnoreCase);
+ }
+
+ public int Count
+ {
+ get { return _data.Count; }
+ }
+
+ public ICollection<string> Keys
+ {
+ get { return _data.Keys; }
+ }
+
+ public ICollection<object> Values
+ {
+ get { return _data.Values; }
+ }
+
+ bool ICollection<KeyValuePair<string, object>>.IsReadOnly
+ {
+ get { return ((ICollection<KeyValuePair<string, object>>)_data).IsReadOnly; }
+ }
+
+ public object this[string key]
+ {
+ get
+ {
+ object value;
+ if (TryGetValue(key, out value))
+ {
+ _initialKeys.Remove(key);
+ return value;
+ }
+ return null;
+ }
+ set
+ {
+ _data[key] = value;
+ _initialKeys.Add(key);
+ }
+ }
+
+ public void Keep()
+ {
+ _retainedKeys.Clear();
+ _retainedKeys.UnionWith(_data.Keys);
+ }
+
+ public void Keep(string key)
+ {
+ _retainedKeys.Add(key);
+ }
+
+ public void Load(ControllerContext controllerContext, ITempDataProvider tempDataProvider)
+ {
+ IDictionary<string, object> providerDictionary = tempDataProvider.LoadTempData(controllerContext);
+ _data = (providerDictionary != null)
+ ? new Dictionary<string, object>(providerDictionary, StringComparer.OrdinalIgnoreCase)
+ : new Dictionary<string, object>(StringComparer.OrdinalIgnoreCase);
+ _initialKeys = new HashSet<string>(_data.Keys, StringComparer.OrdinalIgnoreCase);
+ _retainedKeys.Clear();
+ }
+
+ public object Peek(string key)
+ {
+ object value;
+ _data.TryGetValue(key, out value);
+ return value;
+ }
+
+ public void Save(ControllerContext controllerContext, ITempDataProvider tempDataProvider)
+ {
+ string[] keysToKeep = _initialKeys.Union(_retainedKeys, StringComparer.OrdinalIgnoreCase).ToArray();
+ string[] keysToRemove = _data.Keys.Except(keysToKeep, StringComparer.OrdinalIgnoreCase).ToArray();
+ foreach (string key in keysToRemove)
+ {
+ _data.Remove(key);
+ }
+ tempDataProvider.SaveTempData(controllerContext, _data);
+ }
+
+ public void Add(string key, object value)
+ {
+ _data.Add(key, value);
+ _initialKeys.Add(key);
+ }
+
+ public void Clear()
+ {
+ _data.Clear();
+ _retainedKeys.Clear();
+ _initialKeys.Clear();
+ }
+
+ public bool ContainsKey(string key)
+ {
+ return _data.ContainsKey(key);
+ }
+
+ public bool ContainsValue(object value)
+ {
+ return _data.ContainsValue(value);
+ }
+
+ public IEnumerator<KeyValuePair<string, object>> GetEnumerator()
+ {
+ return new TempDataDictionaryEnumerator(this);
+ }
+
+ public bool Remove(string key)
+ {
+ _retainedKeys.Remove(key);
+ _initialKeys.Remove(key);
+ return _data.Remove(key);
+ }
+
+ public bool TryGetValue(string key, out object value)
+ {
+ _initialKeys.Remove(key);
+ return _data.TryGetValue(key, out value);
+ }
+
+ void ICollection<KeyValuePair<string, object>>.CopyTo(KeyValuePair<string, object>[] array, int index)
+ {
+ ((ICollection<KeyValuePair<string, object>>)_data).CopyTo(array, index);
+ }
+
+ void ICollection<KeyValuePair<string, object>>.Add(KeyValuePair<string, object> keyValuePair)
+ {
+ _initialKeys.Add(keyValuePair.Key);
+ ((ICollection<KeyValuePair<string, object>>)_data).Add(keyValuePair);
+ }
+
+ bool ICollection<KeyValuePair<string, object>>.Contains(KeyValuePair<string, object> keyValuePair)
+ {
+ return ((ICollection<KeyValuePair<string, object>>)_data).Contains(keyValuePair);
+ }
+
+ bool ICollection<KeyValuePair<string, object>>.Remove(KeyValuePair<string, object> keyValuePair)
+ {
+ _initialKeys.Remove(keyValuePair.Key);
+ return ((ICollection<KeyValuePair<string, object>>)_data).Remove(keyValuePair);
+ }
+
+ IEnumerator IEnumerable.GetEnumerator()
+ {
+ return new TempDataDictionaryEnumerator(this);
+ }
+
+ private sealed class TempDataDictionaryEnumerator : IEnumerator<KeyValuePair<string, object>>
+ {
+ private IEnumerator<KeyValuePair<string, object>> _enumerator;
+ private TempDataDictionary _tempData;
+
+ public TempDataDictionaryEnumerator(TempDataDictionary tempData)
+ {
+ _tempData = tempData;
+ _enumerator = _tempData._data.GetEnumerator();
+ }
+
+ public KeyValuePair<string, object> Current
+ {
+ get
+ {
+ KeyValuePair<string, object> kvp = _enumerator.Current;
+ _tempData._initialKeys.Remove(kvp.Key);
+ return kvp;
+ }
+ }
+
+ object IEnumerator.Current
+ {
+ get { return Current; }
+ }
+
+ public bool MoveNext()
+ {
+ return _enumerator.MoveNext();
+ }
+
+ public void Reset()
+ {
+ _enumerator.Reset();
+ }
+
+ void IDisposable.Dispose()
+ {
+ _enumerator.Dispose();
+ }
+ }
+ }
+}
diff --git a/src/System.Web.Mvc/TemplateInfo.cs b/src/System.Web.Mvc/TemplateInfo.cs
new file mode 100644
index 00000000..0f05f1ca
--- /dev/null
+++ b/src/System.Web.Mvc/TemplateInfo.cs
@@ -0,0 +1,58 @@
+using System.Collections.Generic;
+
+namespace System.Web.Mvc
+{
+ public class TemplateInfo
+ {
+ private string _htmlFieldPrefix;
+ private object _formattedModelValue;
+ private HashSet<object> _visitedObjects;
+
+ public object FormattedModelValue
+ {
+ get { return _formattedModelValue ?? String.Empty; }
+ set { _formattedModelValue = value; }
+ }
+
+ public string HtmlFieldPrefix
+ {
+ get { return _htmlFieldPrefix ?? String.Empty; }
+ set { _htmlFieldPrefix = value; }
+ }
+
+ public int TemplateDepth
+ {
+ get { return VisitedObjects.Count; }
+ }
+
+ // DDB #224750 - Keep a collection of visited objects to prevent infinite recursion
+ internal HashSet<object> VisitedObjects
+ {
+ get
+ {
+ if (_visitedObjects == null)
+ {
+ _visitedObjects = new HashSet<object>();
+ }
+ return _visitedObjects;
+ }
+ set { _visitedObjects = value; }
+ }
+
+ public string GetFullHtmlFieldId(string partialFieldName)
+ {
+ return HtmlHelper.GenerateIdFromName(GetFullHtmlFieldName(partialFieldName));
+ }
+
+ public string GetFullHtmlFieldName(string partialFieldName)
+ {
+ // This uses "combine and trim" because either or both of these values might be empty
+ return (HtmlFieldPrefix + "." + (partialFieldName ?? String.Empty)).Trim('.');
+ }
+
+ public bool Visited(ModelMetadata metadata)
+ {
+ return VisitedObjects.Contains(metadata.Model ?? metadata.ModelType);
+ }
+ }
+}
diff --git a/src/System.Web.Mvc/TryGetValueDelegate.cs b/src/System.Web.Mvc/TryGetValueDelegate.cs
new file mode 100644
index 00000000..2092b9e0
--- /dev/null
+++ b/src/System.Web.Mvc/TryGetValueDelegate.cs
@@ -0,0 +1,4 @@
+namespace System.Web.Mvc
+{
+ internal delegate bool TryGetValueDelegate(object dictionary, string key, out object value);
+}
diff --git a/src/System.Web.Mvc/TypeCacheSerializer.cs b/src/System.Web.Mvc/TypeCacheSerializer.cs
new file mode 100644
index 00000000..9067f0d8
--- /dev/null
+++ b/src/System.Web.Mvc/TypeCacheSerializer.cs
@@ -0,0 +1,122 @@
+using System.Collections.Generic;
+using System.Diagnostics.CodeAnalysis;
+using System.IO;
+using System.Linq;
+using System.Reflection;
+using System.Web.Mvc.Properties;
+using System.Xml;
+
+namespace System.Web.Mvc
+{
+ // Processes files with this format:
+ //
+ // <typeCache lastModified=... mvcVersionId=...>
+ // <assembly name=...>
+ // <module versionId=...>
+ // <type>...</type>
+ // </module>
+ // </assembly>
+ // </typeCache>
+ //
+ // This is used to store caches of files between AppDomain resets, leading to improved cold boot time
+ // and more efficient use of memory.
+
+ internal sealed class TypeCacheSerializer
+ {
+ private static readonly Guid _mvcVersionId = typeof(TypeCacheSerializer).Module.ModuleVersionId;
+
+ // used for unit testing
+
+ private DateTime CurrentDate
+ {
+ get { return CurrentDateOverride ?? DateTime.Now; }
+ }
+
+ internal DateTime? CurrentDateOverride { get; set; }
+
+ [SuppressMessage("Microsoft.Performance", "CA1822:MarkMembersAsStatic", Justification = "This is an instance method for consistency with the SerializeTypes() method.")]
+ public List<Type> DeserializeTypes(TextReader input)
+ {
+ XmlDocument doc = new XmlDocument();
+ doc.Load(input);
+ XmlElement rootElement = doc.DocumentElement;
+
+ Guid readMvcVersionId = new Guid(rootElement.Attributes["mvcVersionId"].Value);
+ if (readMvcVersionId != _mvcVersionId)
+ {
+ // The cache is outdated because the cache file was produced by a different version
+ // of MVC.
+ return null;
+ }
+
+ List<Type> deserializedTypes = new List<Type>();
+ foreach (XmlNode assemblyNode in rootElement.ChildNodes)
+ {
+ string assemblyName = assemblyNode.Attributes["name"].Value;
+ Assembly assembly = Assembly.Load(assemblyName);
+
+ foreach (XmlNode moduleNode in assemblyNode.ChildNodes)
+ {
+ Guid moduleVersionId = new Guid(moduleNode.Attributes["versionId"].Value);
+
+ foreach (XmlNode typeNode in moduleNode.ChildNodes)
+ {
+ string typeName = typeNode.InnerText;
+ Type type = assembly.GetType(typeName);
+ if (type == null || type.Module.ModuleVersionId != moduleVersionId)
+ {
+ // The cache is outdated because we couldn't find a previously recorded
+ // type or the type's containing module was modified.
+ return null;
+ }
+ else
+ {
+ deserializedTypes.Add(type);
+ }
+ }
+ }
+ }
+
+ return deserializedTypes;
+ }
+
+ public void SerializeTypes(IEnumerable<Type> types, TextWriter output)
+ {
+ var groupedByAssembly = from type in types
+ group type by type.Module
+ into groupedByModule
+ group groupedByModule by groupedByModule.Key.Assembly;
+
+ XmlDocument doc = new XmlDocument();
+ doc.AppendChild(doc.CreateComment(MvcResources.TypeCache_DoNotModify));
+
+ XmlElement typeCacheElement = doc.CreateElement("typeCache");
+ doc.AppendChild(typeCacheElement);
+ typeCacheElement.SetAttribute("lastModified", CurrentDate.ToString());
+ typeCacheElement.SetAttribute("mvcVersionId", _mvcVersionId.ToString());
+
+ foreach (var assemblyGroup in groupedByAssembly)
+ {
+ XmlElement assemblyElement = doc.CreateElement("assembly");
+ typeCacheElement.AppendChild(assemblyElement);
+ assemblyElement.SetAttribute("name", assemblyGroup.Key.FullName);
+
+ foreach (var moduleGroup in assemblyGroup)
+ {
+ XmlElement moduleElement = doc.CreateElement("module");
+ assemblyElement.AppendChild(moduleElement);
+ moduleElement.SetAttribute("versionId", moduleGroup.Key.ModuleVersionId.ToString());
+
+ foreach (Type type in moduleGroup)
+ {
+ XmlElement typeElement = doc.CreateElement("type");
+ moduleElement.AppendChild(typeElement);
+ typeElement.AppendChild(doc.CreateTextNode(type.FullName));
+ }
+ }
+ }
+
+ doc.Save(output);
+ }
+ }
+}
diff --git a/src/System.Web.Mvc/TypeCacheUtil.cs b/src/System.Web.Mvc/TypeCacheUtil.cs
new file mode 100644
index 00000000..2f9fed55
--- /dev/null
+++ b/src/System.Web.Mvc/TypeCacheUtil.cs
@@ -0,0 +1,104 @@
+using System.Collections;
+using System.Collections.Generic;
+using System.Diagnostics.CodeAnalysis;
+using System.IO;
+using System.Linq;
+using System.Reflection;
+
+namespace System.Web.Mvc
+{
+ internal static class TypeCacheUtil
+ {
+ private static IEnumerable<Type> FilterTypesInAssemblies(IBuildManager buildManager, Predicate<Type> predicate)
+ {
+ // Go through all assemblies referenced by the application and search for types matching a predicate
+ IEnumerable<Type> typesSoFar = Type.EmptyTypes;
+
+ ICollection assemblies = buildManager.GetReferencedAssemblies();
+ foreach (Assembly assembly in assemblies)
+ {
+ Type[] typesInAsm;
+ try
+ {
+ typesInAsm = assembly.GetTypes();
+ }
+ catch (ReflectionTypeLoadException ex)
+ {
+ typesInAsm = ex.Types;
+ }
+ typesSoFar = typesSoFar.Concat(typesInAsm);
+ }
+ return typesSoFar.Where(type => TypeIsPublicClass(type) && predicate(type));
+ }
+
+ public static List<Type> GetFilteredTypesFromAssemblies(string cacheName, Predicate<Type> predicate, IBuildManager buildManager)
+ {
+ TypeCacheSerializer serializer = new TypeCacheSerializer();
+
+ // first, try reading from the cache on disk
+ List<Type> matchingTypes = ReadTypesFromCache(cacheName, predicate, buildManager, serializer);
+ if (matchingTypes != null)
+ {
+ return matchingTypes;
+ }
+
+ // if reading from the cache failed, enumerate over every assembly looking for a matching type
+ matchingTypes = FilterTypesInAssemblies(buildManager, predicate).ToList();
+
+ // finally, save the cache back to disk
+ SaveTypesToCache(cacheName, matchingTypes, buildManager, serializer);
+
+ return matchingTypes;
+ }
+
+ [SuppressMessage("Microsoft.Design", "CA1031:DoNotCatchGeneralExceptionTypes", Justification = "Cache failures are not fatal, and the code should continue executing normally.")]
+ internal static List<Type> ReadTypesFromCache(string cacheName, Predicate<Type> predicate, IBuildManager buildManager, TypeCacheSerializer serializer)
+ {
+ try
+ {
+ Stream stream = buildManager.ReadCachedFile(cacheName);
+ if (stream != null)
+ {
+ using (StreamReader reader = new StreamReader(stream))
+ {
+ List<Type> deserializedTypes = serializer.DeserializeTypes(reader);
+ if (deserializedTypes != null && deserializedTypes.All(type => TypeIsPublicClass(type) && predicate(type)))
+ {
+ // If all read types still match the predicate, success!
+ return deserializedTypes;
+ }
+ }
+ }
+ }
+ catch
+ {
+ }
+
+ return null;
+ }
+
+ [SuppressMessage("Microsoft.Design", "CA1031:DoNotCatchGeneralExceptionTypes", Justification = "Cache failures are not fatal, and the code should continue executing normally.")]
+ internal static void SaveTypesToCache(string cacheName, IList<Type> matchingTypes, IBuildManager buildManager, TypeCacheSerializer serializer)
+ {
+ try
+ {
+ Stream stream = buildManager.CreateCachedFile(cacheName);
+ if (stream != null)
+ {
+ using (StreamWriter writer = new StreamWriter(stream))
+ {
+ serializer.SerializeTypes(matchingTypes, writer);
+ }
+ }
+ }
+ catch
+ {
+ }
+ }
+
+ private static bool TypeIsPublicClass(Type type)
+ {
+ return (type != null && type.IsPublic && type.IsClass && !type.IsAbstract);
+ }
+ }
+}
diff --git a/src/System.Web.Mvc/TypeDescriptorHelper.cs b/src/System.Web.Mvc/TypeDescriptorHelper.cs
new file mode 100644
index 00000000..48d9bcfb
--- /dev/null
+++ b/src/System.Web.Mvc/TypeDescriptorHelper.cs
@@ -0,0 +1,13 @@
+using System.ComponentModel;
+using System.ComponentModel.DataAnnotations;
+
+namespace System.Web.Mvc
+{
+ internal static class TypeDescriptorHelper
+ {
+ public static ICustomTypeDescriptor Get(Type type)
+ {
+ return new AssociatedMetadataTypeTypeDescriptionProvider(type).GetTypeDescriptor(type);
+ }
+ }
+}
diff --git a/src/System.Web.Mvc/TypeHelpers.cs b/src/System.Web.Mvc/TypeHelpers.cs
new file mode 100644
index 00000000..5309b4df
--- /dev/null
+++ b/src/System.Web.Mvc/TypeHelpers.cs
@@ -0,0 +1,146 @@
+using System.Collections;
+using System.Collections.Generic;
+using System.Linq;
+using System.Reflection;
+using System.Threading;
+
+namespace System.Web.Mvc
+{
+ internal static class TypeHelpers
+ {
+ private static readonly Dictionary<Type, TryGetValueDelegate> _tryGetValueDelegateCache = new Dictionary<Type, TryGetValueDelegate>();
+ private static readonly ReaderWriterLockSlim _tryGetValueDelegateCacheLock = new ReaderWriterLockSlim();
+
+ private static readonly MethodInfo _strongTryGetValueImplInfo = typeof(TypeHelpers).GetMethod("StrongTryGetValueImpl", BindingFlags.NonPublic | BindingFlags.Static);
+
+ public static readonly Assembly MsCorLibAssembly = typeof(string).Assembly;
+ public static readonly Assembly MvcAssembly = typeof(Controller).Assembly;
+ public static readonly Assembly SystemWebAssembly = typeof(HttpContext).Assembly;
+
+ // method is used primarily for lighting up new .NET Framework features even if MVC targets the previous version
+ // thisParameter is the 'this' parameter if target method is instance method, should be null for static method
+ public static TDelegate CreateDelegate<TDelegate>(Assembly assembly, string typeName, string methodName, object thisParameter) where TDelegate : class
+ {
+ // ensure target type exists
+ Type targetType = assembly.GetType(typeName, false /* throwOnError */);
+ if (targetType == null)
+ {
+ return null;
+ }
+
+ return CreateDelegate<TDelegate>(targetType, methodName, thisParameter);
+ }
+
+ public static TDelegate CreateDelegate<TDelegate>(Type targetType, string methodName, object thisParameter) where TDelegate : class
+ {
+ // ensure target method exists
+ ParameterInfo[] delegateParameters = typeof(TDelegate).GetMethod("Invoke").GetParameters();
+ Type[] argumentTypes = Array.ConvertAll(delegateParameters, pInfo => pInfo.ParameterType);
+ MethodInfo targetMethod = targetType.GetMethod(methodName, argumentTypes);
+ if (targetMethod == null)
+ {
+ return null;
+ }
+
+ TDelegate d = Delegate.CreateDelegate(typeof(TDelegate), thisParameter, targetMethod, false /* throwOnBindFailure */) as TDelegate;
+ return d;
+ }
+
+ public static TryGetValueDelegate CreateTryGetValueDelegate(Type targetType)
+ {
+ TryGetValueDelegate result;
+
+ _tryGetValueDelegateCacheLock.EnterReadLock();
+ try
+ {
+ if (_tryGetValueDelegateCache.TryGetValue(targetType, out result))
+ {
+ return result;
+ }
+ }
+ finally
+ {
+ _tryGetValueDelegateCacheLock.ExitReadLock();
+ }
+
+ Type dictionaryType = ExtractGenericInterface(targetType, typeof(IDictionary<,>));
+
+ // just wrap a call to the underlying IDictionary<TKey, TValue>.TryGetValue() where string can be cast to TKey
+ if (dictionaryType != null)
+ {
+ Type[] typeArguments = dictionaryType.GetGenericArguments();
+ Type keyType = typeArguments[0];
+ Type returnType = typeArguments[1];
+
+ if (keyType.IsAssignableFrom(typeof(string)))
+ {
+ MethodInfo strongImplInfo = _strongTryGetValueImplInfo.MakeGenericMethod(keyType, returnType);
+ result = (TryGetValueDelegate)Delegate.CreateDelegate(typeof(TryGetValueDelegate), strongImplInfo);
+ }
+ }
+
+ // wrap a call to the underlying IDictionary.Item()
+ if (result == null && typeof(IDictionary).IsAssignableFrom(targetType))
+ {
+ result = TryGetValueFromNonGenericDictionary;
+ }
+
+ _tryGetValueDelegateCacheLock.EnterWriteLock();
+ try
+ {
+ _tryGetValueDelegateCache[targetType] = result;
+ }
+ finally
+ {
+ _tryGetValueDelegateCacheLock.ExitWriteLock();
+ }
+
+ return result;
+ }
+
+ public static Type ExtractGenericInterface(Type queryType, Type interfaceType)
+ {
+ Func<Type, bool> matchesInterface = t => t.IsGenericType && t.GetGenericTypeDefinition() == interfaceType;
+ return (matchesInterface(queryType)) ? queryType : queryType.GetInterfaces().FirstOrDefault(matchesInterface);
+ }
+
+ public static object GetDefaultValue(Type type)
+ {
+ return (TypeAllowsNullValue(type)) ? null : Activator.CreateInstance(type);
+ }
+
+ public static bool IsCompatibleObject<T>(object value)
+ {
+ return (value is T || (value == null && TypeAllowsNullValue(typeof(T))));
+ }
+
+ public static bool IsNullableValueType(Type type)
+ {
+ return Nullable.GetUnderlyingType(type) != null;
+ }
+
+ private static bool StrongTryGetValueImpl<TKey, TValue>(object dictionary, string key, out object value)
+ {
+ IDictionary<TKey, TValue> strongDict = (IDictionary<TKey, TValue>)dictionary;
+
+ TValue strongValue;
+ bool retVal = strongDict.TryGetValue((TKey)(object)key, out strongValue);
+ value = strongValue;
+ return retVal;
+ }
+
+ private static bool TryGetValueFromNonGenericDictionary(object dictionary, string key, out object value)
+ {
+ IDictionary weakDict = (IDictionary)dictionary;
+
+ bool containsKey = weakDict.Contains(key);
+ value = (containsKey) ? weakDict[key] : null;
+ return containsKey;
+ }
+
+ public static bool TypeAllowsNullValue(Type type)
+ {
+ return (!type.IsValueType || IsNullableValueType(type));
+ }
+ }
+}
diff --git a/src/System.Web.Mvc/UnvalidatedRequestValuesAccessor.cs b/src/System.Web.Mvc/UnvalidatedRequestValuesAccessor.cs
new file mode 100644
index 00000000..0c4f775e
--- /dev/null
+++ b/src/System.Web.Mvc/UnvalidatedRequestValuesAccessor.cs
@@ -0,0 +1,4 @@
+namespace System.Web.Mvc
+{
+ internal delegate IUnvalidatedRequestValues UnvalidatedRequestValuesAccessor(ControllerContext controllerContext);
+}
diff --git a/src/System.Web.Mvc/UnvalidatedRequestValuesWrapper.cs b/src/System.Web.Mvc/UnvalidatedRequestValuesWrapper.cs
new file mode 100644
index 00000000..260093a7
--- /dev/null
+++ b/src/System.Web.Mvc/UnvalidatedRequestValuesWrapper.cs
@@ -0,0 +1,32 @@
+using System.Collections.Specialized;
+using System.Web.Helpers;
+
+namespace System.Web.Mvc
+{
+ // Concrete implementation for the IUnvalidatedRequestValues helper interface
+
+ internal sealed class UnvalidatedRequestValuesWrapper : IUnvalidatedRequestValues
+ {
+ private readonly UnvalidatedRequestValues _unvalidatedValues;
+
+ public UnvalidatedRequestValuesWrapper(UnvalidatedRequestValues unvalidatedValues)
+ {
+ _unvalidatedValues = unvalidatedValues;
+ }
+
+ public NameValueCollection Form
+ {
+ get { return _unvalidatedValues.Form; }
+ }
+
+ public NameValueCollection QueryString
+ {
+ get { return _unvalidatedValues.QueryString; }
+ }
+
+ public string this[string key]
+ {
+ get { return _unvalidatedValues[key]; }
+ }
+ }
+}
diff --git a/src/System.Web.Mvc/UrlHelper.cs b/src/System.Web.Mvc/UrlHelper.cs
new file mode 100644
index 00000000..804879f3
--- /dev/null
+++ b/src/System.Web.Mvc/UrlHelper.cs
@@ -0,0 +1,222 @@
+using System.Diagnostics.CodeAnalysis;
+using System.Globalization;
+using System.Web.Mvc.Properties;
+using System.Web.Routing;
+using System.Web.WebPages;
+
+namespace System.Web.Mvc
+{
+ public class UrlHelper
+ {
+ public UrlHelper(RequestContext requestContext)
+ : this(requestContext, RouteTable.Routes)
+ {
+ }
+
+ public UrlHelper(RequestContext requestContext, RouteCollection routeCollection)
+ {
+ if (requestContext == null)
+ {
+ throw new ArgumentNullException("requestContext");
+ }
+ if (routeCollection == null)
+ {
+ throw new ArgumentNullException("routeCollection");
+ }
+ RequestContext = requestContext;
+ RouteCollection = routeCollection;
+ }
+
+ public RequestContext RequestContext { get; private set; }
+
+ public RouteCollection RouteCollection { get; private set; }
+
+ public string Action(string actionName)
+ {
+ return GenerateUrl(null /* routeName */, actionName, null, (RouteValueDictionary)null /* routeValues */);
+ }
+
+ public string Action(string actionName, object routeValues)
+ {
+ return GenerateUrl(null /* routeName */, actionName, null /* controllerName */, new RouteValueDictionary(routeValues));
+ }
+
+ public string Action(string actionName, RouteValueDictionary routeValues)
+ {
+ return GenerateUrl(null /* routeName */, actionName, null /* controllerName */, routeValues);
+ }
+
+ public string Action(string actionName, string controllerName)
+ {
+ return GenerateUrl(null /* routeName */, actionName, controllerName, (RouteValueDictionary)null /* routeValues */);
+ }
+
+ public string Action(string actionName, string controllerName, object routeValues)
+ {
+ return GenerateUrl(null /* routeName */, actionName, controllerName, new RouteValueDictionary(routeValues));
+ }
+
+ public string Action(string actionName, string controllerName, RouteValueDictionary routeValues)
+ {
+ return GenerateUrl(null /* routeName */, actionName, controllerName, routeValues);
+ }
+
+ public string Action(string actionName, string controllerName, object routeValues, string protocol)
+ {
+ return GenerateUrl(null /* routeName */, actionName, controllerName, protocol, null /* hostName */, null /* fragment */, new RouteValueDictionary(routeValues), RouteCollection, RequestContext, true /* includeImplicitMvcValues */);
+ }
+
+ public string Action(string actionName, string controllerName, RouteValueDictionary routeValues, string protocol, string hostName)
+ {
+ return GenerateUrl(null /* routeName */, actionName, controllerName, protocol, hostName, null /* fragment */, routeValues, RouteCollection, RequestContext, true /* includeImplicitMvcValues */);
+ }
+
+ public string Content(string contentPath)
+ {
+ return GenerateContentUrl(contentPath, RequestContext.HttpContext);
+ }
+
+ [SuppressMessage("Microsoft.Design", "CA1055:UriReturnValuesShouldNotBeStrings", Justification = "As the return value will used only for rendering, string return value is more appropriate.")]
+ public static string GenerateContentUrl(string contentPath, HttpContextBase httpContext)
+ {
+ if (String.IsNullOrEmpty(contentPath))
+ {
+ throw new ArgumentException(MvcResources.Common_NullOrEmpty, "contentPath");
+ }
+
+ if (httpContext == null)
+ {
+ throw new ArgumentNullException("httpContext");
+ }
+
+ if (contentPath[0] == '~')
+ {
+ return PathHelpers.GenerateClientUrl(httpContext, contentPath);
+ }
+ else
+ {
+ return contentPath;
+ }
+ }
+
+ //REVIEW: Should we have an overload that takes Uri?
+ [SuppressMessage("Microsoft.Design", "CA1054:UriParametersShouldNotBeStrings", Justification = "Needs to take same parameters as HttpUtility.UrlEncode()")]
+ [SuppressMessage("Microsoft.Performance", "CA1822:MarkMembersAsStatic", Justification = "For consistency, all helpers are instance methods.")]
+ public string Encode(string url)
+ {
+ return HttpUtility.UrlEncode(url);
+ }
+
+ private string GenerateUrl(string routeName, string actionName, string controllerName, RouteValueDictionary routeValues)
+ {
+ return GenerateUrl(routeName, actionName, controllerName, routeValues, RouteCollection, RequestContext, true /* includeImplicitMvcValues */);
+ }
+
+ [SuppressMessage("Microsoft.Design", "CA1055:UriReturnValuesShouldNotBeStrings", Justification = "As the return value will used only for rendering, string return value is more appropriate.")]
+ public static string GenerateUrl(string routeName, string actionName, string controllerName, string protocol, string hostName, string fragment, RouteValueDictionary routeValues, RouteCollection routeCollection, RequestContext requestContext, bool includeImplicitMvcValues)
+ {
+ string url = GenerateUrl(routeName, actionName, controllerName, routeValues, routeCollection, requestContext, includeImplicitMvcValues);
+
+ if (url != null)
+ {
+ if (!String.IsNullOrEmpty(fragment))
+ {
+ url = url + "#" + fragment;
+ }
+
+ if (!String.IsNullOrEmpty(protocol) || !String.IsNullOrEmpty(hostName))
+ {
+ Uri requestUrl = requestContext.HttpContext.Request.Url;
+ protocol = (!String.IsNullOrEmpty(protocol)) ? protocol : Uri.UriSchemeHttp;
+ hostName = (!String.IsNullOrEmpty(hostName)) ? hostName : requestUrl.Host;
+
+ string port = String.Empty;
+ string requestProtocol = requestUrl.Scheme;
+
+ if (String.Equals(protocol, requestProtocol, StringComparison.OrdinalIgnoreCase))
+ {
+ port = requestUrl.IsDefaultPort ? String.Empty : (":" + Convert.ToString(requestUrl.Port, CultureInfo.InvariantCulture));
+ }
+
+ url = protocol + Uri.SchemeDelimiter + hostName + port + url;
+ }
+ }
+
+ return url;
+ }
+
+ [SuppressMessage("Microsoft.Design", "CA1055:UriReturnValuesShouldNotBeStrings", Justification = "As the return value will used only for rendering, string return value is more appropriate.")]
+ public static string GenerateUrl(string routeName, string actionName, string controllerName, RouteValueDictionary routeValues, RouteCollection routeCollection, RequestContext requestContext, bool includeImplicitMvcValues)
+ {
+ if (routeCollection == null)
+ {
+ throw new ArgumentNullException("routeCollection");
+ }
+
+ if (requestContext == null)
+ {
+ throw new ArgumentNullException("requestContext");
+ }
+
+ RouteValueDictionary mergedRouteValues = RouteValuesHelpers.MergeRouteValues(actionName, controllerName, requestContext.RouteData.Values, routeValues, includeImplicitMvcValues);
+
+ VirtualPathData vpd = routeCollection.GetVirtualPathForArea(requestContext, routeName, mergedRouteValues);
+ if (vpd == null)
+ {
+ return null;
+ }
+
+ string modifiedUrl = PathHelpers.GenerateClientUrl(requestContext.HttpContext, vpd.VirtualPath);
+ return modifiedUrl;
+ }
+
+ [SuppressMessage("Microsoft.Design", "CA1054:UriParametersShouldNotBeStrings", MessageId = "0#", Justification = "Response.Redirect() takes its URI as a string parameter.")]
+ public bool IsLocalUrl(string url)
+ {
+ // TODO this should call the System.Web.dll API once it gets added to the framework and MVC takes a dependency on it.
+ return RequestExtensions.IsUrlLocalToHost(RequestContext.HttpContext.Request, url);
+ }
+
+ [SuppressMessage("Microsoft.Design", "CA1055:UriReturnValuesShouldNotBeStrings", Justification = "As the return value will used only for rendering, string return value is more appropriate.")]
+ public string RouteUrl(object routeValues)
+ {
+ return RouteUrl(null /* routeName */, routeValues);
+ }
+
+ [SuppressMessage("Microsoft.Design", "CA1055:UriReturnValuesShouldNotBeStrings", Justification = "As the return value will used only for rendering, string return value is more appropriate.")]
+ public string RouteUrl(RouteValueDictionary routeValues)
+ {
+ return RouteUrl(null /* routeName */, routeValues);
+ }
+
+ [SuppressMessage("Microsoft.Design", "CA1055:UriReturnValuesShouldNotBeStrings", Justification = "As the return value will used only for rendering, string return value is more appropriate.")]
+ public string RouteUrl(string routeName)
+ {
+ return RouteUrl(routeName, (object)null /* routeValues */);
+ }
+
+ [SuppressMessage("Microsoft.Design", "CA1055:UriReturnValuesShouldNotBeStrings", Justification = "As the return value will used only for rendering, string return value is more appropriate.")]
+ public string RouteUrl(string routeName, object routeValues)
+ {
+ return RouteUrl(routeName, routeValues, null /* protocol */);
+ }
+
+ [SuppressMessage("Microsoft.Design", "CA1055:UriReturnValuesShouldNotBeStrings", Justification = "As the return value will used only for rendering, string return value is more appropriate.")]
+ public string RouteUrl(string routeName, RouteValueDictionary routeValues)
+ {
+ return RouteUrl(routeName, routeValues, null /* protocol */, null /* hostName */);
+ }
+
+ [SuppressMessage("Microsoft.Design", "CA1055:UriReturnValuesShouldNotBeStrings", Justification = "As the return value will used only for rendering, string return value is more appropriate.")]
+ public string RouteUrl(string routeName, object routeValues, string protocol)
+ {
+ return GenerateUrl(routeName, null /* actionName */, null /* controllerName */, protocol, null /* hostName */, null /* fragment */, new RouteValueDictionary(routeValues), RouteCollection, RequestContext, false /* includeImplicitMvcValues */);
+ }
+
+ [SuppressMessage("Microsoft.Design", "CA1055:UriReturnValuesShouldNotBeStrings", Justification = "As the return value will used only for rendering, string return value is more appropriate.")]
+ public string RouteUrl(string routeName, RouteValueDictionary routeValues, string protocol, string hostName)
+ {
+ return GenerateUrl(routeName, null /* actionName */, null /* controllerName */, protocol, hostName, null /* fragment */, routeValues, RouteCollection, RequestContext, false /* includeImplicitMvcValues */);
+ }
+ }
+}
diff --git a/src/System.Web.Mvc/UrlParameter.cs b/src/System.Web.Mvc/UrlParameter.cs
new file mode 100644
index 00000000..0b567667
--- /dev/null
+++ b/src/System.Web.Mvc/UrlParameter.cs
@@ -0,0 +1,20 @@
+using System.Diagnostics.CodeAnalysis;
+
+namespace System.Web.Mvc
+{
+ public sealed class UrlParameter
+ {
+ [SuppressMessage("Microsoft.Security", "CA2104:DoNotDeclareReadOnlyMutableReferenceTypes", Justification = "This type is immutable.")]
+ public static readonly UrlParameter Optional = new UrlParameter();
+
+ // singleton constructor
+ private UrlParameter()
+ {
+ }
+
+ public override string ToString()
+ {
+ return String.Empty;
+ }
+ }
+}
diff --git a/src/System.Web.Mvc/UrlRewriterHelper.cs b/src/System.Web.Mvc/UrlRewriterHelper.cs
new file mode 100644
index 00000000..265f7202
--- /dev/null
+++ b/src/System.Web.Mvc/UrlRewriterHelper.cs
@@ -0,0 +1,45 @@
+using System.Collections.Specialized;
+
+namespace System.Web.Mvc
+{
+ internal class UrlRewriterHelper
+ {
+ private const string UrlWasRewrittenServerVar = "IIS_WasUrlRewritten";
+ private const string UrlRewriterEnabledServerVar = "IIS_UrlRewriteModule";
+
+ private object _lockObject = new object();
+ private bool _urlRewriterIsTurnedOnValue;
+ private bool _urlRewriterIsTurnedOnCalculated = false;
+
+ private static bool WasThisRequestRewritten(HttpContextBase httpContext)
+ {
+ NameValueCollection serverVars = httpContext.Request.ServerVariables;
+ bool requestWasRewritten = (serverVars != null && serverVars[UrlWasRewrittenServerVar] != null);
+ return requestWasRewritten;
+ }
+
+ private bool IsUrlRewriterTurnedOn(HttpContextBase httpContext)
+ {
+ // Need to do double-check locking because a single instance of this class is shared in the entire app domain (see PathHelpers)
+ if (!_urlRewriterIsTurnedOnCalculated)
+ {
+ lock (_lockObject)
+ {
+ if (!_urlRewriterIsTurnedOnCalculated)
+ {
+ NameValueCollection serverVars = httpContext.Request.ServerVariables;
+ bool urlRewriterIsEnabled = (serverVars != null && serverVars[UrlRewriterEnabledServerVar] != null);
+ _urlRewriterIsTurnedOnValue = urlRewriterIsEnabled;
+ _urlRewriterIsTurnedOnCalculated = true;
+ }
+ }
+ }
+ return _urlRewriterIsTurnedOnValue;
+ }
+
+ public virtual bool WasRequestRewritten(HttpContextBase httpContext)
+ {
+ return IsUrlRewriterTurnedOn(httpContext) && WasThisRequestRewritten(httpContext);
+ }
+ }
+}
diff --git a/src/System.Web.Mvc/ValidatableObjectAdapter.cs b/src/System.Web.Mvc/ValidatableObjectAdapter.cs
new file mode 100644
index 00000000..88c0137e
--- /dev/null
+++ b/src/System.Web.Mvc/ValidatableObjectAdapter.cs
@@ -0,0 +1,63 @@
+using System.Collections.Generic;
+using System.ComponentModel.DataAnnotations;
+using System.Globalization;
+using System.Linq;
+using System.Web.Mvc.Properties;
+
+namespace System.Web.Mvc
+{
+ public class ValidatableObjectAdapter : ModelValidator
+ {
+ public ValidatableObjectAdapter(ModelMetadata metadata, ControllerContext context)
+ : base(metadata, context)
+ {
+ }
+
+ public override IEnumerable<ModelValidationResult> Validate(object container)
+ {
+ // NOTE: Container is never used here, because IValidatableObject doesn't give you
+ // any way to get access to your container.
+
+ object model = Metadata.Model;
+ if (model == null)
+ {
+ return Enumerable.Empty<ModelValidationResult>();
+ }
+
+ IValidatableObject validatable = model as IValidatableObject;
+ if (validatable == null)
+ {
+ throw new InvalidOperationException(
+ String.Format(
+ CultureInfo.CurrentCulture,
+ MvcResources.ValidatableObjectAdapter_IncompatibleType,
+ typeof(IValidatableObject).FullName,
+ model.GetType().FullName));
+ }
+
+ ValidationContext validationContext = new ValidationContext(validatable, null, null);
+ return ConvertResults(validatable.Validate(validationContext));
+ }
+
+ private IEnumerable<ModelValidationResult> ConvertResults(IEnumerable<ValidationResult> results)
+ {
+ foreach (ValidationResult result in results)
+ {
+ if (result != ValidationResult.Success)
+ {
+ if (result.MemberNames == null || !result.MemberNames.Any())
+ {
+ yield return new ModelValidationResult { Message = result.ErrorMessage };
+ }
+ else
+ {
+ foreach (string memberName in result.MemberNames)
+ {
+ yield return new ModelValidationResult { Message = result.ErrorMessage, MemberName = memberName };
+ }
+ }
+ }
+ }
+ }
+ }
+}
diff --git a/src/System.Web.Mvc/ValidateAntiForgeryTokenAttribute.cs b/src/System.Web.Mvc/ValidateAntiForgeryTokenAttribute.cs
new file mode 100644
index 00000000..b948a0b0
--- /dev/null
+++ b/src/System.Web.Mvc/ValidateAntiForgeryTokenAttribute.cs
@@ -0,0 +1,40 @@
+using System.Diagnostics;
+using System.Web.Helpers;
+
+namespace System.Web.Mvc
+{
+ [AttributeUsage(AttributeTargets.Class | AttributeTargets.Method, AllowMultiple = false, Inherited = true)]
+ public sealed class ValidateAntiForgeryTokenAttribute : FilterAttribute, IAuthorizationFilter
+ {
+ private string _salt;
+
+ public ValidateAntiForgeryTokenAttribute()
+ : this(AntiForgery.Validate)
+ {
+ }
+
+ internal ValidateAntiForgeryTokenAttribute(Action<HttpContextBase, string> validateAction)
+ {
+ Debug.Assert(validateAction != null);
+ ValidateAction = validateAction;
+ }
+
+ public string Salt
+ {
+ get { return _salt ?? String.Empty; }
+ set { _salt = value; }
+ }
+
+ internal Action<HttpContextBase, string> ValidateAction { get; private set; }
+
+ public void OnAuthorization(AuthorizationContext filterContext)
+ {
+ if (filterContext == null)
+ {
+ throw new ArgumentNullException("filterContext");
+ }
+
+ ValidateAction(filterContext.HttpContext, Salt);
+ }
+ }
+}
diff --git a/src/System.Web.Mvc/ValidateInputAttribute.cs b/src/System.Web.Mvc/ValidateInputAttribute.cs
new file mode 100644
index 00000000..67d43afe
--- /dev/null
+++ b/src/System.Web.Mvc/ValidateInputAttribute.cs
@@ -0,0 +1,26 @@
+using System.Diagnostics.CodeAnalysis;
+
+namespace System.Web.Mvc
+{
+ [SuppressMessage("Microsoft.Performance", "CA1813:AvoidUnsealedAttributes", Justification = "No compelling performance reason to seal this type.")]
+ [AttributeUsage(AttributeTargets.Class | AttributeTargets.Method, Inherited = true, AllowMultiple = false)]
+ public class ValidateInputAttribute : FilterAttribute, IAuthorizationFilter
+ {
+ public ValidateInputAttribute(bool enableValidation)
+ {
+ EnableValidation = enableValidation;
+ }
+
+ public bool EnableValidation { get; private set; }
+
+ public virtual void OnAuthorization(AuthorizationContext filterContext)
+ {
+ if (filterContext == null)
+ {
+ throw new ArgumentNullException("filterContext");
+ }
+
+ filterContext.Controller.ValidateRequest = EnableValidation;
+ }
+ }
+}
diff --git a/src/System.Web.Mvc/ValueProviderCollection.cs b/src/System.Web.Mvc/ValueProviderCollection.cs
new file mode 100644
index 00000000..2cb85e92
--- /dev/null
+++ b/src/System.Web.Mvc/ValueProviderCollection.cs
@@ -0,0 +1,78 @@
+using System.Collections.Generic;
+using System.Collections.ObjectModel;
+using System.Linq;
+
+namespace System.Web.Mvc
+{
+ public class ValueProviderCollection : Collection<IValueProvider>, IValueProvider, IUnvalidatedValueProvider, IEnumerableValueProvider
+ {
+ public ValueProviderCollection()
+ {
+ }
+
+ public ValueProviderCollection(IList<IValueProvider> list)
+ : base(list)
+ {
+ }
+
+ public virtual bool ContainsPrefix(string prefix)
+ {
+ return this.Any(vp => vp.ContainsPrefix(prefix));
+ }
+
+ public virtual ValueProviderResult GetValue(string key)
+ {
+ return GetValue(key, skipValidation: false);
+ }
+
+ public virtual ValueProviderResult GetValue(string key, bool skipValidation)
+ {
+ return (from provider in this
+ let result = GetValueFromProvider(provider, key, skipValidation)
+ where result != null
+ select result).FirstOrDefault();
+ }
+
+ public virtual IDictionary<string, string> GetKeysFromPrefix(string prefix)
+ {
+ return (from provider in this
+ let result = GetKeysFromPrefixFromProvider(provider, prefix)
+ where result != null && result.Any()
+ select result).FirstOrDefault() ?? new Dictionary<string, string>();
+ }
+
+ internal static ValueProviderResult GetValueFromProvider(IValueProvider provider, string key, bool skipValidation)
+ {
+ // Since IUnvalidatedValueProvider is a superset of IValueProvider, it's always OK to use the
+ // IUnvalidatedValueProvider-supplied members if they're present. Otherwise just call the
+ // normal IValueProvider members.
+
+ IUnvalidatedValueProvider unvalidatedProvider = provider as IUnvalidatedValueProvider;
+ return (unvalidatedProvider != null) ? unvalidatedProvider.GetValue(key, skipValidation) : provider.GetValue(key);
+ }
+
+ internal static IDictionary<string, string> GetKeysFromPrefixFromProvider(IValueProvider provider, string prefix)
+ {
+ IEnumerableValueProvider enumeratedProvider = provider as IEnumerableValueProvider;
+ return (enumeratedProvider != null) ? enumeratedProvider.GetKeysFromPrefix(prefix) : null;
+ }
+
+ protected override void InsertItem(int index, IValueProvider item)
+ {
+ if (item == null)
+ {
+ throw new ArgumentNullException("item");
+ }
+ base.InsertItem(index, item);
+ }
+
+ protected override void SetItem(int index, IValueProvider item)
+ {
+ if (item == null)
+ {
+ throw new ArgumentNullException("item");
+ }
+ base.SetItem(index, item);
+ }
+ }
+}
diff --git a/src/System.Web.Mvc/ValueProviderDictionary.cs b/src/System.Web.Mvc/ValueProviderDictionary.cs
new file mode 100644
index 00000000..6ea7b6d8
--- /dev/null
+++ b/src/System.Web.Mvc/ValueProviderDictionary.cs
@@ -0,0 +1,217 @@
+using System.Collections;
+using System.Collections.Generic;
+using System.Collections.Specialized;
+using System.Diagnostics.CodeAnalysis;
+using System.Globalization;
+using System.Web.Routing;
+
+namespace System.Web.Mvc
+{
+ [Obsolete("The recommended alternative is to use one of the specific ValueProvider types, such as FormValueProvider.")]
+ public class ValueProviderDictionary : IDictionary<string, ValueProviderResult>, IValueProvider
+ {
+ private readonly Dictionary<string, ValueProviderResult> _dictionary = new Dictionary<string, ValueProviderResult>(StringComparer.OrdinalIgnoreCase);
+
+ public ValueProviderDictionary(ControllerContext controllerContext)
+ {
+ ControllerContext = controllerContext;
+ if (controllerContext != null)
+ {
+ PopulateDictionary();
+ }
+ }
+
+ public ControllerContext ControllerContext { get; private set; }
+
+ public int Count
+ {
+ get { return ((ICollection<KeyValuePair<string, ValueProviderResult>>)Dictionary).Count; }
+ }
+
+ internal Dictionary<string, ValueProviderResult> Dictionary
+ {
+ get { return _dictionary; }
+ }
+
+ public bool IsReadOnly
+ {
+ get { return ((ICollection<KeyValuePair<string, ValueProviderResult>>)Dictionary).IsReadOnly; }
+ }
+
+ public ICollection<string> Keys
+ {
+ get { return Dictionary.Keys; }
+ }
+
+ public ValueProviderResult this[string key]
+ {
+ get
+ {
+ ValueProviderResult result;
+ Dictionary.TryGetValue(key, out result);
+ return result;
+ }
+ set { Dictionary[key] = value; }
+ }
+
+ public ICollection<ValueProviderResult> Values
+ {
+ get { return Dictionary.Values; }
+ }
+
+ public void Add(KeyValuePair<string, ValueProviderResult> item)
+ {
+ ((ICollection<KeyValuePair<string, ValueProviderResult>>)Dictionary).Add(item);
+ }
+
+ public void Add(string key, object value)
+ {
+ string attemptedValue = Convert.ToString(value, CultureInfo.InvariantCulture);
+ ValueProviderResult valueProviderResult = new ValueProviderResult(value, attemptedValue, CultureInfo.InvariantCulture);
+ Add(key, valueProviderResult);
+ }
+
+ public void Add(string key, ValueProviderResult value)
+ {
+ Dictionary.Add(key, value);
+ }
+
+ private void AddToDictionaryIfNotPresent(string key, ValueProviderResult result)
+ {
+ if (!String.IsNullOrEmpty(key))
+ {
+ if (!Dictionary.ContainsKey(key))
+ {
+ Dictionary.Add(key, result);
+ }
+ }
+ }
+
+ public void Clear()
+ {
+ ((ICollection<KeyValuePair<string, ValueProviderResult>>)Dictionary).Clear();
+ }
+
+ public bool Contains(KeyValuePair<string, ValueProviderResult> item)
+ {
+ return ((ICollection<KeyValuePair<string, ValueProviderResult>>)Dictionary).Contains(item);
+ }
+
+ public bool ContainsKey(string key)
+ {
+ return Dictionary.ContainsKey(key);
+ }
+
+ public void CopyTo(KeyValuePair<string, ValueProviderResult>[] array, int arrayIndex)
+ {
+ ((ICollection<KeyValuePair<string, ValueProviderResult>>)Dictionary).CopyTo(array, arrayIndex);
+ }
+
+ public IEnumerator<KeyValuePair<string, ValueProviderResult>> GetEnumerator()
+ {
+ return ((IEnumerable<KeyValuePair<string, ValueProviderResult>>)Dictionary).GetEnumerator();
+ }
+
+ private void PopulateDictionary()
+ {
+ CultureInfo currentCulture = CultureInfo.CurrentCulture;
+ CultureInfo invariantCulture = CultureInfo.InvariantCulture;
+
+ // We use this order of precedence to populate the dictionary:
+ // 1. Request form submission (should be culture-aware)
+ // 2. Values from the RouteData (could be from the typed-in URL or from the route's default values)
+ // 3. URI query string
+
+ NameValueCollection form = ControllerContext.HttpContext.Request.Form;
+ if (form != null)
+ {
+ string[] keys = form.AllKeys;
+ foreach (string key in keys)
+ {
+ string[] rawValue = form.GetValues(key);
+ string attemptedValue = form[key];
+ ValueProviderResult result = new ValueProviderResult(rawValue, attemptedValue, currentCulture);
+ AddToDictionaryIfNotPresent(key, result);
+ }
+ }
+
+ RouteValueDictionary routeValues = ControllerContext.RouteData.Values;
+ if (routeValues != null)
+ {
+ foreach (var kvp in routeValues)
+ {
+ string key = kvp.Key;
+ object rawValue = kvp.Value;
+ string attemptedValue = Convert.ToString(rawValue, invariantCulture);
+ ValueProviderResult result = new ValueProviderResult(rawValue, attemptedValue, invariantCulture);
+ AddToDictionaryIfNotPresent(key, result);
+ }
+ }
+
+ NameValueCollection queryString = ControllerContext.HttpContext.Request.QueryString;
+ if (queryString != null)
+ {
+ string[] keys = queryString.AllKeys;
+ foreach (string key in keys)
+ {
+ string[] rawValue = queryString.GetValues(key);
+ string attemptedValue = queryString[key];
+ ValueProviderResult result = new ValueProviderResult(rawValue, attemptedValue, invariantCulture);
+ AddToDictionaryIfNotPresent(key, result);
+ }
+ }
+ }
+
+ public bool Remove(KeyValuePair<string, ValueProviderResult> item)
+ {
+ return ((ICollection<KeyValuePair<string, ValueProviderResult>>)Dictionary).Remove(item);
+ }
+
+ public bool Remove(string key)
+ {
+ return Dictionary.Remove(key);
+ }
+
+ public bool TryGetValue(string key, out ValueProviderResult value)
+ {
+ return Dictionary.TryGetValue(key, out value);
+ }
+
+ #region IEnumerable Members
+
+ IEnumerator IEnumerable.GetEnumerator()
+ {
+ return ((IEnumerable)Dictionary).GetEnumerator();
+ }
+
+ #endregion
+
+ #region IValueProvider Members
+
+ [SuppressMessage("Microsoft.Design", "CA1033:InterfaceMethodsShouldBeCallableByChildTypes", Justification = "The declaring type is obsolete, so there is little benefit to exposing this as a virtual method.")]
+ bool IValueProvider.ContainsPrefix(string prefix)
+ {
+ if (prefix == null)
+ {
+ throw new ArgumentNullException("prefix");
+ }
+
+ return ValueProviderUtil.CollectionContainsPrefix(Keys, prefix);
+ }
+
+ [SuppressMessage("Microsoft.Design", "CA1033:InterfaceMethodsShouldBeCallableByChildTypes", Justification = "The declaring type is obsolete, so there is little benefit to exposing this as a virtual method.")]
+ ValueProviderResult IValueProvider.GetValue(string key)
+ {
+ if (key == null)
+ {
+ throw new ArgumentNullException("key");
+ }
+
+ ValueProviderResult valueProviderResult;
+ TryGetValue(key, out valueProviderResult);
+ return valueProviderResult;
+ }
+
+ #endregion
+ }
+}
diff --git a/src/System.Web.Mvc/ValueProviderFactories.cs b/src/System.Web.Mvc/ValueProviderFactories.cs
new file mode 100644
index 00000000..ee98658a
--- /dev/null
+++ b/src/System.Web.Mvc/ValueProviderFactories.cs
@@ -0,0 +1,20 @@
+namespace System.Web.Mvc
+{
+ public static class ValueProviderFactories
+ {
+ private static readonly ValueProviderFactoryCollection _factories = new ValueProviderFactoryCollection()
+ {
+ new ChildActionValueProviderFactory(),
+ new FormValueProviderFactory(),
+ new JsonValueProviderFactory(),
+ new RouteDataValueProviderFactory(),
+ new QueryStringValueProviderFactory(),
+ new HttpFileCollectionValueProviderFactory(),
+ };
+
+ public static ValueProviderFactoryCollection Factories
+ {
+ get { return _factories; }
+ }
+ }
+}
diff --git a/src/System.Web.Mvc/ValueProviderFactory.cs b/src/System.Web.Mvc/ValueProviderFactory.cs
new file mode 100644
index 00000000..332b4ae0
--- /dev/null
+++ b/src/System.Web.Mvc/ValueProviderFactory.cs
@@ -0,0 +1,7 @@
+namespace System.Web.Mvc
+{
+ public abstract class ValueProviderFactory
+ {
+ public abstract IValueProvider GetValueProvider(ControllerContext controllerContext);
+ }
+}
diff --git a/src/System.Web.Mvc/ValueProviderFactoryCollection.cs b/src/System.Web.Mvc/ValueProviderFactoryCollection.cs
new file mode 100644
index 00000000..84b57759
--- /dev/null
+++ b/src/System.Web.Mvc/ValueProviderFactoryCollection.cs
@@ -0,0 +1,56 @@
+using System.Collections.Generic;
+using System.Collections.ObjectModel;
+using System.Linq;
+
+namespace System.Web.Mvc
+{
+ public class ValueProviderFactoryCollection : Collection<ValueProviderFactory>
+ {
+ private IResolver<IEnumerable<ValueProviderFactory>> _serviceResolver;
+
+ public ValueProviderFactoryCollection()
+ {
+ _serviceResolver = new MultiServiceResolver<ValueProviderFactory>(() => Items);
+ }
+
+ public ValueProviderFactoryCollection(IList<ValueProviderFactory> list)
+ : base(list)
+ {
+ _serviceResolver = new MultiServiceResolver<ValueProviderFactory>(() => Items);
+ }
+
+ internal ValueProviderFactoryCollection(IResolver<IEnumerable<ValueProviderFactory>> serviceResolver, params ValueProviderFactory[] valueProviderFactories)
+ : base(valueProviderFactories)
+ {
+ _serviceResolver = serviceResolver ?? new MultiServiceResolver<ValueProviderFactory>(() => Items);
+ }
+
+ public IValueProvider GetValueProvider(ControllerContext controllerContext)
+ {
+ var valueProviders = from factory in _serviceResolver.Current
+ let valueProvider = factory.GetValueProvider(controllerContext)
+ where valueProvider != null
+ select valueProvider;
+
+ return new ValueProviderCollection(valueProviders.ToList());
+ }
+
+ protected override void InsertItem(int index, ValueProviderFactory item)
+ {
+ if (item == null)
+ {
+ throw new ArgumentNullException("item");
+ }
+ base.InsertItem(index, item);
+ }
+
+ protected override void SetItem(int index, ValueProviderFactory item)
+ {
+ if (item == null)
+ {
+ throw new ArgumentNullException("item");
+ }
+ base.SetItem(index, item);
+ }
+ }
+}
diff --git a/src/System.Web.Mvc/ValueProviderResult.cs b/src/System.Web.Mvc/ValueProviderResult.cs
new file mode 100644
index 00000000..3a15980b
--- /dev/null
+++ b/src/System.Web.Mvc/ValueProviderResult.cs
@@ -0,0 +1,163 @@
+using System.Collections;
+using System.ComponentModel;
+using System.Globalization;
+using System.Web.Mvc.Properties;
+
+namespace System.Web.Mvc
+{
+ [Serializable]
+ public class ValueProviderResult
+ {
+ private static readonly CultureInfo _staticCulture = CultureInfo.InvariantCulture;
+ private CultureInfo _instanceCulture;
+
+ // default constructor so that subclassed types can set the properties themselves
+ protected ValueProviderResult()
+ {
+ }
+
+ public ValueProviderResult(object rawValue, string attemptedValue, CultureInfo culture)
+ {
+ RawValue = rawValue;
+ AttemptedValue = attemptedValue;
+ Culture = culture;
+ }
+
+ public string AttemptedValue { get; protected set; }
+
+ public CultureInfo Culture
+ {
+ get
+ {
+ if (_instanceCulture == null)
+ {
+ _instanceCulture = _staticCulture;
+ }
+ return _instanceCulture;
+ }
+ protected set { _instanceCulture = value; }
+ }
+
+ public object RawValue { get; protected set; }
+
+ private static object ConvertSimpleType(CultureInfo culture, object value, Type destinationType)
+ {
+ if (value == null || destinationType.IsInstanceOfType(value))
+ {
+ return value;
+ }
+
+ // if this is a user-input value but the user didn't type anything, return no value
+ string valueAsString = value as string;
+ if (valueAsString != null && valueAsString.Trim().Length == 0)
+ {
+ return null;
+ }
+
+ TypeConverter converter = TypeDescriptor.GetConverter(destinationType);
+ bool canConvertFrom = converter.CanConvertFrom(value.GetType());
+ if (!canConvertFrom)
+ {
+ converter = TypeDescriptor.GetConverter(value.GetType());
+ }
+ if (!(canConvertFrom || converter.CanConvertTo(destinationType)))
+ {
+ // EnumConverter cannot convert integer, so we verify manually
+ if (destinationType.IsEnum && value is int)
+ {
+ return Enum.ToObject(destinationType, (int)value);
+ }
+
+ // In case of a Nullable object, we try again with its underlying type.
+ Type underlyingType = Nullable.GetUnderlyingType(destinationType);
+ if (underlyingType != null)
+ {
+ return ConvertSimpleType(culture, value, underlyingType);
+ }
+
+ string message = String.Format(CultureInfo.CurrentCulture, MvcResources.ValueProviderResult_NoConverterExists,
+ value.GetType().FullName, destinationType.FullName);
+ throw new InvalidOperationException(message);
+ }
+
+ try
+ {
+ object convertedValue = (canConvertFrom)
+ ? converter.ConvertFrom(null /* context */, culture, value)
+ : converter.ConvertTo(null /* context */, culture, value, destinationType);
+ return convertedValue;
+ }
+ catch (Exception ex)
+ {
+ string message = String.Format(CultureInfo.CurrentCulture, MvcResources.ValueProviderResult_ConversionThrew,
+ value.GetType().FullName, destinationType.FullName);
+ throw new InvalidOperationException(message, ex);
+ }
+ }
+
+ public object ConvertTo(Type type)
+ {
+ return ConvertTo(type, null /* culture */);
+ }
+
+ public virtual object ConvertTo(Type type, CultureInfo culture)
+ {
+ if (type == null)
+ {
+ throw new ArgumentNullException("type");
+ }
+
+ CultureInfo cultureToUse = culture ?? Culture;
+ return UnwrapPossibleArrayType(cultureToUse, RawValue, type);
+ }
+
+ private static object UnwrapPossibleArrayType(CultureInfo culture, object value, Type destinationType)
+ {
+ if (value == null || destinationType.IsInstanceOfType(value))
+ {
+ return value;
+ }
+
+ // array conversion results in four cases, as below
+ Array valueAsArray = value as Array;
+ if (destinationType.IsArray)
+ {
+ Type destinationElementType = destinationType.GetElementType();
+ if (valueAsArray != null)
+ {
+ // case 1: both destination + source type are arrays, so convert each element
+ IList converted = Array.CreateInstance(destinationElementType, valueAsArray.Length);
+ for (int i = 0; i < valueAsArray.Length; i++)
+ {
+ converted[i] = ConvertSimpleType(culture, valueAsArray.GetValue(i), destinationElementType);
+ }
+ return converted;
+ }
+ else
+ {
+ // case 2: destination type is array but source is single element, so wrap element in array + convert
+ object element = ConvertSimpleType(culture, value, destinationElementType);
+ IList converted = Array.CreateInstance(destinationElementType, 1);
+ converted[0] = element;
+ return converted;
+ }
+ }
+ else if (valueAsArray != null)
+ {
+ // case 3: destination type is single element but source is array, so extract first element + convert
+ if (valueAsArray.Length > 0)
+ {
+ value = valueAsArray.GetValue(0);
+ return ConvertSimpleType(culture, value, destinationType);
+ }
+ else
+ {
+ // case 3(a): source is empty array, so can't perform conversion
+ return null;
+ }
+ }
+ // case 4: both destination + source type are single elements, so convert
+ return ConvertSimpleType(culture, value, destinationType);
+ }
+ }
+}
diff --git a/src/System.Web.Mvc/ValueProviderUtil.cs b/src/System.Web.Mvc/ValueProviderUtil.cs
new file mode 100644
index 00000000..4db98104
--- /dev/null
+++ b/src/System.Web.Mvc/ValueProviderUtil.cs
@@ -0,0 +1,119 @@
+using System.Collections.Generic;
+
+namespace System.Web.Mvc
+{
+ internal static class ValueProviderUtil
+ {
+ // Given "foo.bar[baz].quux", this method will return:
+ // - "foo.bar[baz].quux"
+ // - "foo.bar[baz]"
+ // - "foo.bar"
+ // - "foo"
+ public static IEnumerable<string> GetPrefixes(string key)
+ {
+ yield return key;
+ for (int i = key.Length - 1; i >= 0; i--)
+ {
+ switch (key[i])
+ {
+ case '.':
+ case '[':
+ yield return key.Substring(0, i);
+ break;
+ }
+ }
+ }
+
+ public static bool CollectionContainsPrefix(IEnumerable<string> collection, string prefix)
+ {
+ foreach (string key in collection)
+ {
+ if (key != null)
+ {
+ if (prefix.Length == 0)
+ {
+ return true; // shortcut - non-null key matches empty prefix
+ }
+
+ if (key.StartsWith(prefix, StringComparison.OrdinalIgnoreCase))
+ {
+ if (key.Length == prefix.Length)
+ {
+ return true; // exact match
+ }
+ else
+ {
+ switch (key[prefix.Length])
+ {
+ case '.': // known separator characters
+ case '[':
+ return true;
+ }
+ }
+ }
+ }
+ }
+
+ return false; // nothing found
+ }
+
+ // Given "foo.bar", "foo.hello", "something.other", foo[abc].baz and asking for prefix "foo" will return:
+ // - "bar"/"foo.bar"
+ // - "hello"/"foo.hello"
+ // - "abc"/"foo[abc]"
+ public static IDictionary<string, string> GetKeysFromPrefix(IEnumerable<string> collection, string prefix)
+ {
+ IDictionary<string, string> keys = new Dictionary<string, string>();
+ foreach (var entry in collection)
+ {
+ if (entry != null)
+ {
+ string key = null;
+ string fullName = null;
+
+ if (entry.Length == prefix.Length)
+ {
+ // No key in this entry
+ continue;
+ }
+
+ if (entry.StartsWith(prefix, StringComparison.OrdinalIgnoreCase))
+ {
+ int keyPosition = prefix.Length + 1;
+ switch (entry[prefix.Length])
+ {
+ case '.':
+ int dotPosition = entry.IndexOf('.', keyPosition);
+ if (dotPosition == -1)
+ {
+ dotPosition = entry.Length;
+ }
+
+ key = entry.Substring(keyPosition, dotPosition - keyPosition);
+ fullName = entry.Substring(0, dotPosition);
+ break;
+ case '[':
+ int bracketPosition = entry.IndexOf(']', keyPosition);
+ if (bracketPosition == -1)
+ {
+ // Malformed for dictionary
+ continue;
+ }
+
+ key = entry.Substring(keyPosition, bracketPosition - keyPosition);
+ fullName = entry.Substring(0, bracketPosition + 1);
+ break;
+ }
+
+ if (!keys.ContainsKey(key))
+ {
+ keys.Add(key, fullName);
+ }
+ }
+ }
+ }
+
+ return keys;
+ }
+ }
+}
diff --git a/src/System.Web.Mvc/ViewContext.cs b/src/System.Web.Mvc/ViewContext.cs
new file mode 100644
index 00000000..87e5200e
--- /dev/null
+++ b/src/System.Web.Mvc/ViewContext.cs
@@ -0,0 +1,281 @@
+using System.Collections;
+using System.Collections.Generic;
+using System.Diagnostics.CodeAnalysis;
+using System.Globalization;
+using System.IO;
+using System.Web.WebPages.Scope;
+
+namespace System.Web.Mvc
+{
+ public class ViewContext : ControllerContext
+ {
+ private const string ClientValidationScript = @"<script type=""text/javascript"">
+//<![CDATA[
+if (!window.mvcClientValidationMetadata) {{ window.mvcClientValidationMetadata = []; }}
+window.mvcClientValidationMetadata.push({0});
+//]]>
+</script>";
+
+ internal static readonly string ClientValidationKeyName = "ClientValidationEnabled";
+ internal static readonly string UnobtrusiveJavaScriptKeyName = "UnobtrusiveJavaScriptEnabled";
+
+ // Some values have to be stored in HttpContext.Items in order to be propagated between calls
+ // to RenderPartial(), RenderAction(), etc.
+ private static readonly object _formContextKey = new object();
+ private static readonly object _lastFormNumKey = new object();
+
+ private Func<IDictionary<object, object>> _scopeThunk;
+ private IDictionary<object, object> _transientScope;
+
+ private DynamicViewDataDictionary _dynamicViewDataDictionary;
+ private Func<string> _formIdGenerator;
+
+ // We need a default FormContext if the user uses html <form> instead of an MvcForm
+ private FormContext _defaultFormContext = new FormContext();
+
+ // parameterless constructor used for mocking
+ public ViewContext()
+ {
+ }
+
+ [SuppressMessage("Microsoft.Usage", "CA2214:DoNotCallOverridableMethodsInConstructors", Justification = "The virtual property setters are only to support mocking frameworks, in which case this constructor shouldn't be called anyway.")]
+ public ViewContext(ControllerContext controllerContext, IView view, ViewDataDictionary viewData, TempDataDictionary tempData, TextWriter writer)
+ : base(controllerContext)
+ {
+ if (controllerContext == null)
+ {
+ throw new ArgumentNullException("controllerContext");
+ }
+ if (view == null)
+ {
+ throw new ArgumentNullException("view");
+ }
+ if (viewData == null)
+ {
+ throw new ArgumentNullException("viewData");
+ }
+ if (tempData == null)
+ {
+ throw new ArgumentNullException("tempData");
+ }
+ if (writer == null)
+ {
+ throw new ArgumentNullException("writer");
+ }
+
+ View = view;
+ ViewData = viewData;
+ Writer = writer;
+ TempData = tempData;
+ }
+
+ public virtual bool ClientValidationEnabled
+ {
+ get { return GetClientValidationEnabled(Scope, HttpContext); }
+ set { SetClientValidationEnabled(value, Scope, HttpContext); }
+ }
+
+ public virtual FormContext FormContext
+ {
+ get
+ {
+ // Never return a null form context, this is important for validation purposes
+ return HttpContext.Items[_formContextKey] as FormContext ?? _defaultFormContext;
+ }
+ set { HttpContext.Items[_formContextKey] = value; }
+ }
+
+ internal Func<string> FormIdGenerator
+ {
+ get
+ {
+ if (_formIdGenerator == null)
+ {
+ _formIdGenerator = DefaultFormIdGenerator;
+ }
+ return _formIdGenerator;
+ }
+ set { _formIdGenerator = value; }
+ }
+
+ internal static Func<IDictionary<object, object>> GlobalScopeThunk { get; set; }
+
+ private IDictionary<object, object> Scope
+ {
+ get
+ {
+ if (ScopeThunk != null)
+ {
+ return ScopeThunk();
+ }
+ if (_transientScope == null)
+ {
+ _transientScope = new Dictionary<object, object>();
+ }
+ return _transientScope;
+ }
+ }
+
+ internal Func<IDictionary<object, object>> ScopeThunk
+ {
+ get { return _scopeThunk ?? GlobalScopeThunk; }
+ set { _scopeThunk = value; }
+ }
+
+ [SuppressMessage("Microsoft.Usage", "CA2227:CollectionPropertiesShouldBeReadOnly", Justification = "The property setter is only here to support mocking this type and should not be called at runtime.")]
+ public virtual TempDataDictionary TempData { get; set; }
+
+ public virtual bool UnobtrusiveJavaScriptEnabled
+ {
+ get { return GetUnobtrusiveJavaScriptEnabled(Scope, HttpContext); }
+ set { SetUnobtrusiveJavaScriptEnabled(value, Scope, HttpContext); }
+ }
+
+ public virtual IView View { get; set; }
+
+ public dynamic ViewBag
+ {
+ get
+ {
+ if (_dynamicViewDataDictionary == null)
+ {
+ _dynamicViewDataDictionary = new DynamicViewDataDictionary(() => ViewData);
+ }
+ return _dynamicViewDataDictionary;
+ }
+ }
+
+ [SuppressMessage("Microsoft.Usage", "CA2227:CollectionPropertiesShouldBeReadOnly", Justification = "The property setter is only here to support mocking this type and should not be called at runtime.")]
+ public virtual ViewDataDictionary ViewData { get; set; }
+
+ public virtual TextWriter Writer { get; set; }
+
+ private string DefaultFormIdGenerator()
+ {
+ int formNum = IncrementFormCount(HttpContext.Items);
+ return String.Format(CultureInfo.InvariantCulture, "form{0}", formNum);
+ }
+
+ internal static bool GetClientValidationEnabled(IDictionary<object, object> scope = null, HttpContextBase httpContext = null)
+ {
+ return ScopeCache.Get(scope, httpContext).ClientValidationEnabled;
+ }
+
+ internal FormContext GetFormContextForClientValidation()
+ {
+ return (ClientValidationEnabled) ? FormContext : null;
+ }
+
+ internal static bool GetUnobtrusiveJavaScriptEnabled(IDictionary<object, object> scope = null, HttpContextBase httpContext = null)
+ {
+ return ScopeCache.Get(scope, httpContext).UnobtrusiveJavaScriptEnabled;
+ }
+
+ private static int IncrementFormCount(IDictionary items)
+ {
+ object lastFormNum = items[_lastFormNumKey];
+ int newFormNum = (lastFormNum != null) ? ((int)lastFormNum) + 1 : 0;
+ items[_lastFormNumKey] = newFormNum;
+ return newFormNum;
+ }
+
+ public void OutputClientValidation()
+ {
+ FormContext formContext = GetFormContextForClientValidation();
+ if (formContext == null || UnobtrusiveJavaScriptEnabled)
+ {
+ return; // do nothing
+ }
+
+ string scriptWithCorrectNewLines = ClientValidationScript.Replace("\r\n", Environment.NewLine);
+ string validationJson = formContext.GetJsonValidationMetadata();
+ string formatted = String.Format(CultureInfo.InvariantCulture, scriptWithCorrectNewLines, validationJson);
+
+ Writer.Write(formatted);
+ }
+
+ internal static void SetClientValidationEnabled(bool enabled, IDictionary<object, object> scope = null, HttpContextBase httpContext = null)
+ {
+ ScopeCache.Get(scope, httpContext).ClientValidationEnabled = enabled;
+ }
+
+ internal static void SetUnobtrusiveJavaScriptEnabled(bool enabled, IDictionary<object, object> scope = null, HttpContextBase httpContext = null)
+ {
+ ScopeCache.Get(scope, httpContext).UnobtrusiveJavaScriptEnabled = enabled;
+ }
+
+ private static TValue ScopeGet<TValue>(IDictionary<object, object> scope, string name, TValue defaultValue = default(TValue))
+ {
+ object result;
+ if (scope.TryGetValue(name, out result))
+ {
+ return (TValue)Convert.ChangeType(result, typeof(TValue), CultureInfo.InvariantCulture);
+ }
+ return defaultValue;
+ }
+
+ private sealed class ScopeCache
+ {
+ private static readonly object _cacheKey = new object();
+ private bool _clientValidationEnabled;
+ private IDictionary<object, object> _scope;
+ private bool _unobtrusiveJavaScriptEnabled;
+
+ private ScopeCache(IDictionary<object, object> scope)
+ {
+ _scope = scope;
+
+ _clientValidationEnabled = ScopeGet(scope, ClientValidationKeyName, false);
+ _unobtrusiveJavaScriptEnabled = ScopeGet(scope, UnobtrusiveJavaScriptKeyName, false);
+ }
+
+ public bool ClientValidationEnabled
+ {
+ get { return _clientValidationEnabled; }
+ set
+ {
+ _clientValidationEnabled = value;
+ _scope[ClientValidationKeyName] = value;
+ }
+ }
+
+ public bool UnobtrusiveJavaScriptEnabled
+ {
+ get { return _unobtrusiveJavaScriptEnabled; }
+ set
+ {
+ _unobtrusiveJavaScriptEnabled = value;
+ _scope[UnobtrusiveJavaScriptKeyName] = value;
+ }
+ }
+
+ public static ScopeCache Get(IDictionary<object, object> scope, HttpContextBase httpContext)
+ {
+ if (httpContext == null && Web.HttpContext.Current != null)
+ {
+ httpContext = new HttpContextWrapper(Web.HttpContext.Current);
+ }
+
+ ScopeCache result = null;
+ scope = scope ?? ScopeStorage.CurrentScope;
+
+ if (httpContext != null)
+ {
+ result = httpContext.Items[_cacheKey] as ScopeCache;
+ }
+
+ if (result == null || result._scope != scope)
+ {
+ result = new ScopeCache(scope);
+
+ if (httpContext != null)
+ {
+ httpContext.Items[_cacheKey] = result;
+ }
+ }
+
+ return result;
+ }
+ }
+ }
+}
diff --git a/src/System.Web.Mvc/ViewDataDictionary.cs b/src/System.Web.Mvc/ViewDataDictionary.cs
new file mode 100644
index 00000000..d104b33f
--- /dev/null
+++ b/src/System.Web.Mvc/ViewDataDictionary.cs
@@ -0,0 +1,387 @@
+using System.Collections;
+using System.Collections.Generic;
+using System.ComponentModel;
+using System.Diagnostics.CodeAnalysis;
+using System.Globalization;
+using System.Web.Mvc.Properties;
+
+namespace System.Web.Mvc
+{
+ // TODO: Unit test ModelState interaction with VDD
+
+ public class ViewDataDictionary : IDictionary<string, object>
+ {
+ private readonly Dictionary<string, object> _innerDictionary = new Dictionary<string, object>(StringComparer.OrdinalIgnoreCase);
+ private readonly ModelStateDictionary _modelState = new ModelStateDictionary();
+ private object _model;
+ private ModelMetadata _modelMetadata;
+ private TemplateInfo _templateMetadata;
+
+ public ViewDataDictionary()
+ : this((object)null)
+ {
+ }
+
+ [SuppressMessage("Microsoft.Usage", "CA2214:DoNotCallOverridableMethodsInConstructors", Justification = "See note on SetModel() method.")]
+ public ViewDataDictionary(object model)
+ {
+ Model = model;
+ }
+
+ [SuppressMessage("Microsoft.Usage", "CA2214:DoNotCallOverridableMethodsInConstructors", Justification = "See note on SetModel() method.")]
+ public ViewDataDictionary(ViewDataDictionary dictionary)
+ {
+ if (dictionary == null)
+ {
+ throw new ArgumentNullException("dictionary");
+ }
+
+ foreach (var entry in dictionary)
+ {
+ _innerDictionary.Add(entry.Key, entry.Value);
+ }
+ foreach (var entry in dictionary.ModelState)
+ {
+ ModelState.Add(entry.Key, entry.Value);
+ }
+
+ Model = dictionary.Model;
+ TemplateInfo = dictionary.TemplateInfo;
+
+ // PERF: Don't unnecessarily instantiate the model metadata
+ _modelMetadata = dictionary._modelMetadata;
+ }
+
+ public int Count
+ {
+ get { return _innerDictionary.Count; }
+ }
+
+ public bool IsReadOnly
+ {
+ get { return ((IDictionary<string, object>)_innerDictionary).IsReadOnly; }
+ }
+
+ public ICollection<string> Keys
+ {
+ get { return _innerDictionary.Keys; }
+ }
+
+ public object Model
+ {
+ get { return _model; }
+ set
+ {
+ _modelMetadata = null;
+ SetModel(value);
+ }
+ }
+
+ public virtual ModelMetadata ModelMetadata
+ {
+ get
+ {
+ if (_modelMetadata == null && _model != null)
+ {
+ _modelMetadata = ModelMetadataProviders.Current.GetMetadataForType(() => _model, _model.GetType());
+ }
+ return _modelMetadata;
+ }
+ set { _modelMetadata = value; }
+ }
+
+ public ModelStateDictionary ModelState
+ {
+ get { return _modelState; }
+ }
+
+ public TemplateInfo TemplateInfo
+ {
+ get
+ {
+ if (_templateMetadata == null)
+ {
+ _templateMetadata = new TemplateInfo();
+ }
+ return _templateMetadata;
+ }
+ set { _templateMetadata = value; }
+ }
+
+ public ICollection<object> Values
+ {
+ get { return _innerDictionary.Values; }
+ }
+
+ public object this[string key]
+ {
+ get
+ {
+ object value;
+ _innerDictionary.TryGetValue(key, out value);
+ return value;
+ }
+ set { _innerDictionary[key] = value; }
+ }
+
+ public void Add(KeyValuePair<string, object> item)
+ {
+ ((IDictionary<string, object>)_innerDictionary).Add(item);
+ }
+
+ public void Add(string key, object value)
+ {
+ _innerDictionary.Add(key, value);
+ }
+
+ public void Clear()
+ {
+ _innerDictionary.Clear();
+ }
+
+ public bool Contains(KeyValuePair<string, object> item)
+ {
+ return ((IDictionary<string, object>)_innerDictionary).Contains(item);
+ }
+
+ public bool ContainsKey(string key)
+ {
+ return _innerDictionary.ContainsKey(key);
+ }
+
+ public void CopyTo(KeyValuePair<string, object>[] array, int arrayIndex)
+ {
+ ((IDictionary<string, object>)_innerDictionary).CopyTo(array, arrayIndex);
+ }
+
+ public object Eval(string expression)
+ {
+ ViewDataInfo info = GetViewDataInfo(expression);
+ return (info != null) ? info.Value : null;
+ }
+
+ public string Eval(string expression, string format)
+ {
+ object value = Eval(expression);
+ return FormatValueInternal(value, format);
+ }
+
+ internal static string FormatValueInternal(object value, string format)
+ {
+ if (value == null)
+ {
+ return String.Empty;
+ }
+
+ if (String.IsNullOrEmpty(format))
+ {
+ return Convert.ToString(value, CultureInfo.CurrentCulture);
+ }
+ else
+ {
+ return String.Format(CultureInfo.CurrentCulture, format, value);
+ }
+ }
+
+ public IEnumerator<KeyValuePair<string, object>> GetEnumerator()
+ {
+ return _innerDictionary.GetEnumerator();
+ }
+
+ public ViewDataInfo GetViewDataInfo(string expression)
+ {
+ if (String.IsNullOrEmpty(expression))
+ {
+ throw new ArgumentException(MvcResources.Common_NullOrEmpty, "expression");
+ }
+
+ return ViewDataEvaluator.Eval(this, expression);
+ }
+
+ public bool Remove(KeyValuePair<string, object> item)
+ {
+ return ((IDictionary<string, object>)_innerDictionary).Remove(item);
+ }
+
+ public bool Remove(string key)
+ {
+ return _innerDictionary.Remove(key);
+ }
+
+ // This method will execute before the derived type's instance constructor executes. Derived types must
+ // be aware of this and should plan accordingly. For example, the logic in SetModel() should be simple
+ // enough so as not to depend on the "this" pointer referencing a fully constructed object.
+ protected virtual void SetModel(object value)
+ {
+ _model = value;
+ }
+
+ public bool TryGetValue(string key, out object value)
+ {
+ return _innerDictionary.TryGetValue(key, out value);
+ }
+
+ internal static class ViewDataEvaluator
+ {
+ public static ViewDataInfo Eval(ViewDataDictionary vdd, string expression)
+ {
+ //Given an expression "foo.bar.baz" we look up the following (pseudocode):
+ // this["foo.bar.baz.quux"]
+ // this["foo.bar.baz"]["quux"]
+ // this["foo.bar"]["baz.quux]
+ // this["foo.bar"]["baz"]["quux"]
+ // this["foo"]["bar.baz.quux"]
+ // this["foo"]["bar.baz"]["quux"]
+ // this["foo"]["bar"]["baz.quux"]
+ // this["foo"]["bar"]["baz"]["quux"]
+
+ ViewDataInfo evaluated = EvalComplexExpression(vdd, expression);
+ return evaluated;
+ }
+
+ private static ViewDataInfo EvalComplexExpression(object indexableObject, string expression)
+ {
+ foreach (ExpressionPair expressionPair in GetRightToLeftExpressions(expression))
+ {
+ string subExpression = expressionPair.Left;
+ string postExpression = expressionPair.Right;
+
+ ViewDataInfo subTargetInfo = GetPropertyValue(indexableObject, subExpression);
+ if (subTargetInfo != null)
+ {
+ if (String.IsNullOrEmpty(postExpression))
+ {
+ return subTargetInfo;
+ }
+
+ if (subTargetInfo.Value != null)
+ {
+ ViewDataInfo potential = EvalComplexExpression(subTargetInfo.Value, postExpression);
+ if (potential != null)
+ {
+ return potential;
+ }
+ }
+ }
+ }
+ return null;
+ }
+
+ private static IEnumerable<ExpressionPair> GetRightToLeftExpressions(string expression)
+ {
+ // Produces an enumeration of all the combinations of complex property names
+ // given a complex expression. See the list above for an example of the result
+ // of the enumeration.
+
+ yield return new ExpressionPair(expression, String.Empty);
+
+ int lastDot = expression.LastIndexOf('.');
+
+ string subExpression = expression;
+ string postExpression = String.Empty;
+
+ while (lastDot > -1)
+ {
+ subExpression = expression.Substring(0, lastDot);
+ postExpression = expression.Substring(lastDot + 1);
+ yield return new ExpressionPair(subExpression, postExpression);
+
+ lastDot = subExpression.LastIndexOf('.');
+ }
+ }
+
+ private static ViewDataInfo GetIndexedPropertyValue(object indexableObject, string key)
+ {
+ IDictionary<string, object> dict = indexableObject as IDictionary<string, object>;
+ object value = null;
+ bool success = false;
+
+ if (dict != null)
+ {
+ success = dict.TryGetValue(key, out value);
+ }
+ else
+ {
+ TryGetValueDelegate tgvDel = TypeHelpers.CreateTryGetValueDelegate(indexableObject.GetType());
+ if (tgvDel != null)
+ {
+ success = tgvDel(indexableObject, key, out value);
+ }
+ }
+
+ if (success)
+ {
+ return new ViewDataInfo()
+ {
+ Container = indexableObject,
+ Value = value
+ };
+ }
+
+ return null;
+ }
+
+ private static ViewDataInfo GetPropertyValue(object container, string propertyName)
+ {
+ // This method handles one "segment" of a complex property expression
+
+ // First, we try to evaluate the property based on its indexer
+ ViewDataInfo value = GetIndexedPropertyValue(container, propertyName);
+ if (value != null)
+ {
+ return value;
+ }
+
+ // If the indexer didn't return anything useful, continue...
+
+ // If the container is a ViewDataDictionary then treat its Model property
+ // as the container instead of the ViewDataDictionary itself.
+ ViewDataDictionary vdd = container as ViewDataDictionary;
+ if (vdd != null)
+ {
+ container = vdd.Model;
+ }
+
+ // If the container is null, we're out of options
+ if (container == null)
+ {
+ return null;
+ }
+
+ // Second, we try to use PropertyDescriptors and treat the expression as a property name
+ PropertyDescriptor descriptor = TypeDescriptor.GetProperties(container).Find(propertyName, true);
+ if (descriptor == null)
+ {
+ return null;
+ }
+
+ return new ViewDataInfo(() => descriptor.GetValue(container))
+ {
+ Container = container,
+ PropertyDescriptor = descriptor
+ };
+ }
+
+ private struct ExpressionPair
+ {
+ public readonly string Left;
+ public readonly string Right;
+
+ public ExpressionPair(string left, string right)
+ {
+ Left = left;
+ Right = right;
+ }
+ }
+ }
+
+ #region IEnumerable Members
+
+ IEnumerator IEnumerable.GetEnumerator()
+ {
+ return ((IEnumerable)_innerDictionary).GetEnumerator();
+ }
+
+ #endregion
+ }
+}
diff --git a/src/System.Web.Mvc/ViewDataDictionary`1.cs b/src/System.Web.Mvc/ViewDataDictionary`1.cs
new file mode 100644
index 00000000..966cc77b
--- /dev/null
+++ b/src/System.Web.Mvc/ViewDataDictionary`1.cs
@@ -0,0 +1,60 @@
+namespace System.Web.Mvc
+{
+ public class ViewDataDictionary<TModel> : ViewDataDictionary
+ {
+ public ViewDataDictionary()
+ :
+ base(default(TModel))
+ {
+ }
+
+ public ViewDataDictionary(TModel model)
+ :
+ base(model)
+ {
+ }
+
+ public ViewDataDictionary(ViewDataDictionary viewDataDictionary)
+ :
+ base(viewDataDictionary)
+ {
+ }
+
+ public new TModel Model
+ {
+ get { return (TModel)base.Model; }
+ set { SetModel(value); }
+ }
+
+ public override ModelMetadata ModelMetadata
+ {
+ get
+ {
+ ModelMetadata result = base.ModelMetadata;
+ if (result == null)
+ {
+ result = base.ModelMetadata = ModelMetadataProviders.Current.GetMetadataForType(null, typeof(TModel));
+ }
+ return result;
+ }
+ set { base.ModelMetadata = value; }
+ }
+
+ protected override void SetModel(object value)
+ {
+ bool castWillSucceed = TypeHelpers.IsCompatibleObject<TModel>(value);
+
+ if (castWillSucceed)
+ {
+ base.SetModel((TModel)value);
+ }
+ else
+ {
+ InvalidOperationException exception = (value != null)
+ ? Error.ViewDataDictionary_WrongTModelType(value.GetType(), typeof(TModel))
+ : Error.ViewDataDictionary_ModelCannotBeNull(typeof(TModel));
+ throw exception;
+ }
+ }
+ }
+}
diff --git a/src/System.Web.Mvc/ViewDataInfo.cs b/src/System.Web.Mvc/ViewDataInfo.cs
new file mode 100644
index 00000000..c508ffda
--- /dev/null
+++ b/src/System.Web.Mvc/ViewDataInfo.cs
@@ -0,0 +1,42 @@
+using System.ComponentModel;
+
+namespace System.Web.Mvc
+{
+ public class ViewDataInfo
+ {
+ private object _value;
+ private Func<object> _valueAccessor;
+
+ public ViewDataInfo()
+ {
+ }
+
+ public ViewDataInfo(Func<object> valueAccessor)
+ {
+ _valueAccessor = valueAccessor;
+ }
+
+ public object Container { get; set; }
+
+ public PropertyDescriptor PropertyDescriptor { get; set; }
+
+ public object Value
+ {
+ get
+ {
+ if (_valueAccessor != null)
+ {
+ _value = _valueAccessor();
+ _valueAccessor = null;
+ }
+
+ return _value;
+ }
+ set
+ {
+ _value = value;
+ _valueAccessor = null;
+ }
+ }
+ }
+}
diff --git a/src/System.Web.Mvc/ViewEngineCollection.cs b/src/System.Web.Mvc/ViewEngineCollection.cs
new file mode 100644
index 00000000..6dcd254b
--- /dev/null
+++ b/src/System.Web.Mvc/ViewEngineCollection.cs
@@ -0,0 +1,133 @@
+using System.Collections.Generic;
+using System.Collections.ObjectModel;
+using System.Linq;
+using System.Web.Mvc.Properties;
+
+namespace System.Web.Mvc
+{
+ public class ViewEngineCollection : Collection<IViewEngine>
+ {
+ private IResolver<IEnumerable<IViewEngine>> _serviceResolver;
+
+ public ViewEngineCollection()
+ {
+ _serviceResolver = new MultiServiceResolver<IViewEngine>(() => Items);
+ }
+
+ public ViewEngineCollection(IList<IViewEngine> list)
+ : base(list)
+ {
+ _serviceResolver = new MultiServiceResolver<IViewEngine>(() => Items);
+ }
+
+ internal ViewEngineCollection(IResolver<IEnumerable<IViewEngine>> serviceResolver, params IViewEngine[] engines)
+ : base(engines)
+ {
+ _serviceResolver = serviceResolver ?? new MultiServiceResolver<IViewEngine>(() => Items);
+ }
+
+ private IEnumerable<IViewEngine> CombinedItems
+ {
+ get { return _serviceResolver.Current; }
+ }
+
+ protected override void InsertItem(int index, IViewEngine item)
+ {
+ if (item == null)
+ {
+ throw new ArgumentNullException("item");
+ }
+ base.InsertItem(index, item);
+ }
+
+ protected override void SetItem(int index, IViewEngine item)
+ {
+ if (item == null)
+ {
+ throw new ArgumentNullException("item");
+ }
+ base.SetItem(index, item);
+ }
+
+ private ViewEngineResult Find(Func<IViewEngine, ViewEngineResult> cacheLocator, Func<IViewEngine, ViewEngineResult> locator)
+ {
+ // First, look up using the cacheLocator and do not track the searched paths in non-matching view engines
+ // Then, look up using the normal locator and track the searched paths so that an error view engine can be returned
+ return Find(cacheLocator, trackSearchedPaths: false)
+ ?? Find(locator, trackSearchedPaths: true);
+ }
+
+ private ViewEngineResult Find(Func<IViewEngine, ViewEngineResult> lookup, bool trackSearchedPaths)
+ {
+ // Returns
+ // 1st result
+ // OR list of searched paths (if trackSearchedPaths == true)
+ // OR null
+ ViewEngineResult result;
+
+ List<string> searched = null;
+ if (trackSearchedPaths)
+ {
+ searched = new List<string>();
+ }
+
+ foreach (IViewEngine engine in CombinedItems)
+ {
+ if (engine != null)
+ {
+ result = lookup(engine);
+
+ if (result.View != null)
+ {
+ return result;
+ }
+
+ if (trackSearchedPaths)
+ {
+ searched.AddRange(result.SearchedLocations);
+ }
+ }
+ }
+
+ if (trackSearchedPaths)
+ {
+ // Remove duplicate search paths since multiple view engines could have potentially looked at the same path
+ return new ViewEngineResult(searched.Distinct().ToList());
+ }
+ else
+ {
+ return null;
+ }
+ }
+
+ public virtual ViewEngineResult FindPartialView(ControllerContext controllerContext, string partialViewName)
+ {
+ if (controllerContext == null)
+ {
+ throw new ArgumentNullException("controllerContext");
+ }
+ if (String.IsNullOrEmpty(partialViewName))
+ {
+ throw new ArgumentException(MvcResources.Common_NullOrEmpty, "partialViewName");
+ }
+
+ return Find(e => e.FindPartialView(controllerContext, partialViewName, true),
+ e => e.FindPartialView(controllerContext, partialViewName, false));
+ }
+
+ public virtual ViewEngineResult FindView(ControllerContext controllerContext, string viewName, string masterName)
+ {
+ if (controllerContext == null)
+ {
+ throw new ArgumentNullException("controllerContext");
+ }
+ if (String.IsNullOrEmpty(viewName))
+ {
+ throw new ArgumentException(MvcResources.Common_NullOrEmpty, "viewName");
+ }
+
+ return Find(e => e.FindView(controllerContext, viewName, masterName, true),
+ e => e.FindView(controllerContext, viewName, masterName, false));
+ }
+ }
+}
diff --git a/src/System.Web.Mvc/ViewEngineResult.cs b/src/System.Web.Mvc/ViewEngineResult.cs
new file mode 100644
index 00000000..7e9e081a
--- /dev/null
+++ b/src/System.Web.Mvc/ViewEngineResult.cs
@@ -0,0 +1,38 @@
+using System.Collections.Generic;
+
+namespace System.Web.Mvc
+{
+ public class ViewEngineResult
+ {
+ public ViewEngineResult(IEnumerable<string> searchedLocations)
+ {
+ if (searchedLocations == null)
+ {
+ throw new ArgumentNullException("searchedLocations");
+ }
+
+ SearchedLocations = searchedLocations;
+ }
+
+ public ViewEngineResult(IView view, IViewEngine viewEngine)
+ {
+ if (view == null)
+ {
+ throw new ArgumentNullException("view");
+ }
+ if (viewEngine == null)
+ {
+ throw new ArgumentNullException("viewEngine");
+ }
+
+ View = view;
+ ViewEngine = viewEngine;
+ }
+
+ public IEnumerable<string> SearchedLocations { get; private set; }
+
+ public IView View { get; private set; }
+
+ public IViewEngine ViewEngine { get; private set; }
+ }
+}
diff --git a/src/System.Web.Mvc/ViewEngines.cs b/src/System.Web.Mvc/ViewEngines.cs
new file mode 100644
index 00000000..090f20bc
--- /dev/null
+++ b/src/System.Web.Mvc/ViewEngines.cs
@@ -0,0 +1,16 @@
+namespace System.Web.Mvc
+{
+ public static class ViewEngines
+ {
+ private static readonly ViewEngineCollection _engines = new ViewEngineCollection
+ {
+ new WebFormViewEngine(),
+ new RazorViewEngine(),
+ };
+
+ public static ViewEngineCollection Engines
+ {
+ get { return _engines; }
+ }
+ }
+}
diff --git a/src/System.Web.Mvc/ViewMasterPage.cs b/src/System.Web.Mvc/ViewMasterPage.cs
new file mode 100644
index 00000000..c3d7c66b
--- /dev/null
+++ b/src/System.Web.Mvc/ViewMasterPage.cs
@@ -0,0 +1,68 @@
+using System.Globalization;
+using System.Web.Mvc.Properties;
+using System.Web.UI;
+
+namespace System.Web.Mvc
+{
+ [FileLevelControlBuilder(typeof(ViewMasterPageControlBuilder))]
+ public class ViewMasterPage : MasterPage
+ {
+ public AjaxHelper<object> Ajax
+ {
+ get { return ViewPage.Ajax; }
+ }
+
+ public HtmlHelper<object> Html
+ {
+ get { return ViewPage.Html; }
+ }
+
+ public object Model
+ {
+ get { return ViewData.Model; }
+ }
+
+ public TempDataDictionary TempData
+ {
+ get { return ViewPage.TempData; }
+ }
+
+ public UrlHelper Url
+ {
+ get { return ViewPage.Url; }
+ }
+
+ public dynamic ViewBag
+ {
+ get { return ViewPage.ViewBag; }
+ }
+
+ public ViewContext ViewContext
+ {
+ get { return ViewPage.ViewContext; }
+ }
+
+ public ViewDataDictionary ViewData
+ {
+ get { return ViewPage.ViewData; }
+ }
+
+ internal ViewPage ViewPage
+ {
+ get
+ {
+ ViewPage viewPage = Page as ViewPage;
+ if (viewPage == null)
+ {
+ throw new InvalidOperationException(String.Format(CultureInfo.CurrentCulture, MvcResources.ViewMasterPage_RequiresViewPage));
+ }
+ return viewPage;
+ }
+ }
+
+ public HtmlTextWriter Writer
+ {
+ get { return ViewPage.Writer; }
+ }
+ }
+}
diff --git a/src/System.Web.Mvc/ViewMasterPageControlBuilder.cs b/src/System.Web.Mvc/ViewMasterPageControlBuilder.cs
new file mode 100644
index 00000000..a41556ef
--- /dev/null
+++ b/src/System.Web.Mvc/ViewMasterPageControlBuilder.cs
@@ -0,0 +1,18 @@
+using System.CodeDom;
+using System.Web.UI;
+
+namespace System.Web.Mvc
+{
+ internal sealed class ViewMasterPageControlBuilder : FileLevelMasterPageControlBuilder, IMvcControlBuilder
+ {
+ public string Inherits { get; set; }
+
+ public override void ProcessGeneratedCode(CodeCompileUnit codeCompileUnit, CodeTypeDeclaration baseType, CodeTypeDeclaration derivedType, CodeMemberMethod buildMethod, CodeMemberMethod dataBindingMethod)
+ {
+ if (!String.IsNullOrWhiteSpace(Inherits))
+ {
+ derivedType.BaseTypes[0] = new CodeTypeReference(Inherits);
+ }
+ }
+ }
+}
diff --git a/src/System.Web.Mvc/ViewMasterPage`1.cs b/src/System.Web.Mvc/ViewMasterPage`1.cs
new file mode 100644
index 00000000..d514c7d7
--- /dev/null
+++ b/src/System.Web.Mvc/ViewMasterPage`1.cs
@@ -0,0 +1,50 @@
+namespace System.Web.Mvc
+{
+ public class ViewMasterPage<TModel> : ViewMasterPage
+ {
+ private AjaxHelper<TModel> _ajaxHelper;
+ private HtmlHelper<TModel> _htmlHelper;
+ private ViewDataDictionary<TModel> _viewData;
+
+ public new AjaxHelper<TModel> Ajax
+ {
+ get
+ {
+ if (_ajaxHelper == null)
+ {
+ _ajaxHelper = new AjaxHelper<TModel>(ViewContext, ViewPage);
+ }
+ return _ajaxHelper;
+ }
+ }
+
+ public new HtmlHelper<TModel> Html
+ {
+ get
+ {
+ if (_htmlHelper == null)
+ {
+ _htmlHelper = new HtmlHelper<TModel>(ViewContext, ViewPage);
+ }
+ return _htmlHelper;
+ }
+ }
+
+ public new TModel Model
+ {
+ get { return ViewData.Model; }
+ }
+
+ public new ViewDataDictionary<TModel> ViewData
+ {
+ get
+ {
+ if (_viewData == null)
+ {
+ _viewData = new ViewDataDictionary<TModel>(ViewPage.ViewData);
+ }
+ return _viewData;
+ }
+ }
+ }
+}
diff --git a/src/System.Web.Mvc/ViewPage.cs b/src/System.Web.Mvc/ViewPage.cs
new file mode 100644
index 00000000..79a4fec7
--- /dev/null
+++ b/src/System.Web.Mvc/ViewPage.cs
@@ -0,0 +1,427 @@
+using System.Diagnostics.CodeAnalysis;
+using System.Globalization;
+using System.IO;
+using System.Text;
+using System.Web.UI;
+
+namespace System.Web.Mvc
+{
+ [FileLevelControlBuilder(typeof(ViewPageControlBuilder))]
+ public class ViewPage : Page, IViewDataContainer
+ {
+ [ThreadStatic]
+ private static int _nextId;
+
+ private DynamicViewDataDictionary _dynamicViewData;
+ private string _masterLocation;
+
+ private ViewDataDictionary _viewData;
+
+ public AjaxHelper<object> Ajax { get; set; }
+
+ public HtmlHelper<object> Html { get; set; }
+
+ public string MasterLocation
+ {
+ get { return _masterLocation ?? String.Empty; }
+ set { _masterLocation = value; }
+ }
+
+ public object Model
+ {
+ get { return ViewData.Model; }
+ }
+
+ public TempDataDictionary TempData
+ {
+ get { return ViewContext.TempData; }
+ }
+
+ public UrlHelper Url { get; set; }
+
+ public dynamic ViewBag
+ {
+ get
+ {
+ if (_dynamicViewData == null)
+ {
+ _dynamicViewData = new DynamicViewDataDictionary(() => ViewData);
+ }
+ return _dynamicViewData;
+ }
+ }
+
+ public ViewContext ViewContext { get; set; }
+
+ [SuppressMessage("Microsoft.Usage", "CA2227:CollectionPropertiesShouldBeReadOnly", Justification = "This is the mechanism by which the ViewPage gets its ViewDataDictionary object.")]
+ public ViewDataDictionary ViewData
+ {
+ get
+ {
+ if (_viewData == null)
+ {
+ SetViewData(new ViewDataDictionary());
+ }
+ return _viewData;
+ }
+ set { SetViewData(value); }
+ }
+
+ public HtmlTextWriter Writer { get; private set; }
+
+ public virtual void InitHelpers()
+ {
+ Ajax = new AjaxHelper<object>(ViewContext, this);
+ Html = new HtmlHelper<object>(ViewContext, this);
+ Url = new UrlHelper(ViewContext.RequestContext);
+ }
+
+ internal static string NextId()
+ {
+ return (++_nextId).ToString(CultureInfo.InvariantCulture);
+ }
+
+ protected override void OnPreInit(EventArgs e)
+ {
+ base.OnPreInit(e);
+
+ if (!String.IsNullOrEmpty(MasterLocation))
+ {
+ MasterPageFile = MasterLocation;
+ }
+ }
+
+ public override void ProcessRequest(HttpContext context)
+ {
+ // Tracing requires IDs to be unique.
+ ID = NextId();
+
+ base.ProcessRequest(context);
+ }
+
+ protected override void Render(HtmlTextWriter writer)
+ {
+ Writer = writer;
+ try
+ {
+ base.Render(writer);
+ }
+ finally
+ {
+ Writer = null;
+ }
+ }
+
+ [SuppressMessage("Microsoft.Reliability", "CA2000:Dispose objects before losing scope", Justification = "The object is disposed in the finally block of the method")]
+ public virtual void RenderView(ViewContext viewContext)
+ {
+ ViewContext = viewContext;
+ InitHelpers();
+
+ bool createdSwitchWriter = false;
+ SwitchWriter switchWriter = viewContext.HttpContext.Response.Output as SwitchWriter;
+
+ try
+ {
+ if (switchWriter == null)
+ {
+ switchWriter = new SwitchWriter();
+ createdSwitchWriter = true;
+ }
+
+ using (switchWriter.Scope(viewContext.Writer))
+ {
+ if (createdSwitchWriter)
+ {
+ // It's safe to reset the _nextId within a Server.Execute() since it pushes a new TraceContext onto
+ // the stack, so there won't be an ID conflict.
+ int originalNextId = _nextId;
+ try
+ {
+ _nextId = 0;
+ viewContext.HttpContext.Server.Execute(HttpHandlerUtil.WrapForServerExecute(this), switchWriter, true /* preserveForm */);
+ }
+ finally
+ {
+ // Restore the original _nextId in case this isn't actually the outermost view, since resetting
+ // the _nextId may now cause trace ID conflicts in the outer view.
+ _nextId = originalNextId;
+ }
+ }
+ else
+ {
+ ProcessRequest(HttpContext.Current);
+ }
+ }
+ }
+ finally
+ {
+ if (createdSwitchWriter)
+ {
+ switchWriter.Dispose();
+ }
+ }
+ }
+
+ [SuppressMessage("Microsoft.Usage", "CA1801:ReviewUnusedParameters", MessageId = "textWriter", Justification = "This method existed in MVC 1.0 and has been deprecated.")]
+ [SuppressMessage("Microsoft.Performance", "CA1822:MarkMembersAsStatic", Justification = "This method existed in MVC 1.0 and has been deprecated.")]
+ [Obsolete("The TextWriter is now provided by the ViewContext object passed to the RenderView method.", true /* error */)]
+ public void SetTextWriter(TextWriter textWriter)
+ {
+ // this is now a no-op
+ }
+
+ protected virtual void SetViewData(ViewDataDictionary viewData)
+ {
+ _viewData = viewData;
+ }
+
+ internal class SwitchWriter : TextWriter
+ {
+ public SwitchWriter()
+ : base(CultureInfo.CurrentCulture)
+ {
+ }
+
+ public override Encoding Encoding
+ {
+ get { return InnerWriter.Encoding; }
+ }
+
+ public override IFormatProvider FormatProvider
+ {
+ get { return InnerWriter.FormatProvider; }
+ }
+
+ internal TextWriter InnerWriter { get; set; }
+
+ public override string NewLine
+ {
+ get { return InnerWriter.NewLine; }
+ set { InnerWriter.NewLine = value; }
+ }
+
+ public override void Close()
+ {
+ InnerWriter.Close();
+ }
+
+ public override void Flush()
+ {
+ InnerWriter.Flush();
+ }
+
+ public IDisposable Scope(TextWriter writer)
+ {
+ WriterScope scope = new WriterScope(this, InnerWriter);
+
+ try
+ {
+ if (writer != this)
+ {
+ InnerWriter = writer;
+ }
+
+ return scope;
+ }
+ catch
+ {
+ scope.Dispose();
+ throw;
+ }
+ }
+
+ public override void Write(bool value)
+ {
+ InnerWriter.Write(value);
+ }
+
+ public override void Write(char value)
+ {
+ InnerWriter.Write(value);
+ }
+
+ public override void Write(char[] buffer)
+ {
+ InnerWriter.Write(buffer);
+ }
+
+ public override void Write(char[] buffer, int index, int count)
+ {
+ InnerWriter.Write(buffer, index, count);
+ }
+
+ public override void Write(decimal value)
+ {
+ InnerWriter.Write(value);
+ }
+
+ public override void Write(double value)
+ {
+ InnerWriter.Write(value);
+ }
+
+ public override void Write(float value)
+ {
+ InnerWriter.Write(value);
+ }
+
+ public override void Write(int value)
+ {
+ InnerWriter.Write(value);
+ }
+
+ public override void Write(long value)
+ {
+ InnerWriter.Write(value);
+ }
+
+ public override void Write(object value)
+ {
+ InnerWriter.Write(value);
+ }
+
+ public override void Write(string format, object arg0)
+ {
+ InnerWriter.Write(format, arg0);
+ }
+
+ public override void Write(string format, object arg0, object arg1)
+ {
+ InnerWriter.Write(format, arg0, arg1);
+ }
+
+ public override void Write(string format, object arg0, object arg1, object arg2)
+ {
+ InnerWriter.Write(format, arg0, arg1, arg2);
+ }
+
+ public override void Write(string format, params object[] arg)
+ {
+ InnerWriter.Write(format, arg);
+ }
+
+ public override void Write(string value)
+ {
+ InnerWriter.Write(value);
+ }
+
+ public override void Write(uint value)
+ {
+ InnerWriter.Write(value);
+ }
+
+ public override void Write(ulong value)
+ {
+ InnerWriter.Write(value);
+ }
+
+ public override void WriteLine()
+ {
+ InnerWriter.WriteLine();
+ }
+
+ public override void WriteLine(bool value)
+ {
+ InnerWriter.WriteLine(value);
+ }
+
+ public override void WriteLine(char value)
+ {
+ InnerWriter.WriteLine(value);
+ }
+
+ public override void WriteLine(char[] buffer)
+ {
+ InnerWriter.WriteLine(buffer);
+ }
+
+ public override void WriteLine(char[] buffer, int index, int count)
+ {
+ InnerWriter.WriteLine(buffer, index, count);
+ }
+
+ public override void WriteLine(decimal value)
+ {
+ InnerWriter.WriteLine(value);
+ }
+
+ public override void WriteLine(double value)
+ {
+ InnerWriter.WriteLine(value);
+ }
+
+ public override void WriteLine(float value)
+ {
+ InnerWriter.WriteLine(value);
+ }
+
+ public override void WriteLine(int value)
+ {
+ InnerWriter.WriteLine(value);
+ }
+
+ public override void WriteLine(long value)
+ {
+ InnerWriter.WriteLine(value);
+ }
+
+ public override void WriteLine(object value)
+ {
+ InnerWriter.WriteLine(value);
+ }
+
+ public override void WriteLine(string format, object arg0)
+ {
+ InnerWriter.WriteLine(format, arg0);
+ }
+
+ public override void WriteLine(string format, object arg0, object arg1)
+ {
+ InnerWriter.WriteLine(format, arg0, arg1);
+ }
+
+ public override void WriteLine(string format, object arg0, object arg1, object arg2)
+ {
+ InnerWriter.WriteLine(format, arg0, arg1, arg2);
+ }
+
+ public override void WriteLine(string format, params object[] arg)
+ {
+ InnerWriter.WriteLine(format, arg);
+ }
+
+ public override void WriteLine(string value)
+ {
+ InnerWriter.WriteLine(value);
+ }
+
+ public override void WriteLine(uint value)
+ {
+ InnerWriter.WriteLine(value);
+ }
+
+ public override void WriteLine(ulong value)
+ {
+ InnerWriter.WriteLine(value);
+ }
+
+ private sealed class WriterScope : IDisposable
+ {
+ private SwitchWriter _switchWriter;
+ private TextWriter _writerToRestore;
+
+ public WriterScope(SwitchWriter switchWriter, TextWriter writerToRestore)
+ {
+ _switchWriter = switchWriter;
+ _writerToRestore = writerToRestore;
+ }
+
+ public void Dispose()
+ {
+ _switchWriter.InnerWriter = _writerToRestore;
+ }
+ }
+ }
+ }
+}
diff --git a/src/System.Web.Mvc/ViewPageControlBuilder.cs b/src/System.Web.Mvc/ViewPageControlBuilder.cs
new file mode 100644
index 00000000..0dd309a3
--- /dev/null
+++ b/src/System.Web.Mvc/ViewPageControlBuilder.cs
@@ -0,0 +1,18 @@
+using System.CodeDom;
+using System.Web.UI;
+
+namespace System.Web.Mvc
+{
+ internal sealed class ViewPageControlBuilder : FileLevelPageControlBuilder, IMvcControlBuilder
+ {
+ public string Inherits { get; set; }
+
+ public override void ProcessGeneratedCode(CodeCompileUnit codeCompileUnit, CodeTypeDeclaration baseType, CodeTypeDeclaration derivedType, CodeMemberMethod buildMethod, CodeMemberMethod dataBindingMethod)
+ {
+ if (!String.IsNullOrWhiteSpace(Inherits))
+ {
+ derivedType.BaseTypes[0] = new CodeTypeReference(Inherits);
+ }
+ }
+ }
+}
diff --git a/src/System.Web.Mvc/ViewPage`1.cs b/src/System.Web.Mvc/ViewPage`1.cs
new file mode 100644
index 00000000..2e5be343
--- /dev/null
+++ b/src/System.Web.Mvc/ViewPage`1.cs
@@ -0,0 +1,47 @@
+using System.Diagnostics.CodeAnalysis;
+
+namespace System.Web.Mvc
+{
+ public class ViewPage<TModel> : ViewPage
+ {
+ private ViewDataDictionary<TModel> _viewData;
+
+ public new AjaxHelper<TModel> Ajax { get; set; }
+
+ public new HtmlHelper<TModel> Html { get; set; }
+
+ public new TModel Model
+ {
+ get { return ViewData.Model; }
+ }
+
+ [SuppressMessage("Microsoft.Usage", "CA2227:CollectionPropertiesShouldBeReadOnly", Justification = "This is settable for unit testing purposes")]
+ public new ViewDataDictionary<TModel> ViewData
+ {
+ get
+ {
+ if (_viewData == null)
+ {
+ SetViewData(new ViewDataDictionary<TModel>());
+ }
+ return _viewData;
+ }
+ set { SetViewData(value); }
+ }
+
+ public override void InitHelpers()
+ {
+ base.InitHelpers();
+
+ Ajax = new AjaxHelper<TModel>(ViewContext, this);
+ Html = new HtmlHelper<TModel>(ViewContext, this);
+ }
+
+ protected override void SetViewData(ViewDataDictionary viewData)
+ {
+ _viewData = new ViewDataDictionary<TModel>(viewData);
+
+ base.SetViewData(_viewData);
+ }
+ }
+}
diff --git a/src/System.Web.Mvc/ViewResult.cs b/src/System.Web.Mvc/ViewResult.cs
new file mode 100644
index 00000000..c462d6df
--- /dev/null
+++ b/src/System.Web.Mvc/ViewResult.cs
@@ -0,0 +1,36 @@
+using System.Globalization;
+using System.Text;
+using System.Web.Mvc.Properties;
+
+namespace System.Web.Mvc
+{
+ public class ViewResult : ViewResultBase
+ {
+ private string _masterName;
+
+ public string MasterName
+ {
+ get { return _masterName ?? String.Empty; }
+ set { _masterName = value; }
+ }
+
+ protected override ViewEngineResult FindView(ControllerContext context)
+ {
+ ViewEngineResult result = ViewEngineCollection.FindView(context, ViewName, MasterName);
+ if (result.View != null)
+ {
+ return result;
+ }
+
+ // we need to generate an exception containing all the locations we searched
+ StringBuilder locationsText = new StringBuilder();
+ foreach (string location in result.SearchedLocations)
+ {
+ locationsText.AppendLine();
+ locationsText.Append(location);
+ }
+ throw new InvalidOperationException(String.Format(CultureInfo.CurrentCulture,
+ MvcResources.Common_ViewNotFound, ViewName, locationsText));
+ }
+ }
+}
diff --git a/src/System.Web.Mvc/ViewResultBase.cs b/src/System.Web.Mvc/ViewResultBase.cs
new file mode 100644
index 00000000..d7650aa8
--- /dev/null
+++ b/src/System.Web.Mvc/ViewResultBase.cs
@@ -0,0 +1,105 @@
+using System.Diagnostics.CodeAnalysis;
+using System.IO;
+
+namespace System.Web.Mvc
+{
+ public abstract class ViewResultBase : ActionResult
+ {
+ private DynamicViewDataDictionary _dynamicViewData;
+ private TempDataDictionary _tempData;
+ private ViewDataDictionary _viewData;
+ private ViewEngineCollection _viewEngineCollection;
+ private string _viewName;
+
+ public object Model
+ {
+ get { return ViewData.Model; }
+ }
+
+ [SuppressMessage("Microsoft.Usage", "CA2227:CollectionPropertiesShouldBeReadOnly", Justification = "This entire type is meant to be mutable.")]
+ public TempDataDictionary TempData
+ {
+ get
+ {
+ if (_tempData == null)
+ {
+ _tempData = new TempDataDictionary();
+ }
+ return _tempData;
+ }
+ set { _tempData = value; }
+ }
+
+ public IView View { get; set; }
+
+ public dynamic ViewBag
+ {
+ get
+ {
+ if (_dynamicViewData == null)
+ {
+ _dynamicViewData = new DynamicViewDataDictionary(() => ViewData);
+ }
+ return _dynamicViewData;
+ }
+ }
+
+ [SuppressMessage("Microsoft.Usage", "CA2227:CollectionPropertiesShouldBeReadOnly", Justification = "This entire type is meant to be mutable.")]
+ public ViewDataDictionary ViewData
+ {
+ get
+ {
+ if (_viewData == null)
+ {
+ _viewData = new ViewDataDictionary();
+ }
+ return _viewData;
+ }
+ set { _viewData = value; }
+ }
+
+ [SuppressMessage("Microsoft.Usage", "CA2227:CollectionPropertiesShouldBeReadOnly", Justification = "This entire type is meant to be mutable.")]
+ public ViewEngineCollection ViewEngineCollection
+ {
+ get { return _viewEngineCollection ?? ViewEngines.Engines; }
+ set { _viewEngineCollection = value; }
+ }
+
+ public string ViewName
+ {
+ get { return _viewName ?? String.Empty; }
+ set { _viewName = value; }
+ }
+
+ public override void ExecuteResult(ControllerContext context)
+ {
+ if (context == null)
+ {
+ throw new ArgumentNullException("context");
+ }
+ if (String.IsNullOrEmpty(ViewName))
+ {
+ ViewName = context.RouteData.GetRequiredString("action");
+ }
+
+ ViewEngineResult result = null;
+
+ if (View == null)
+ {
+ result = FindView(context);
+ View = result.View;
+ }
+
+ TextWriter writer = context.HttpContext.Response.Output;
+ ViewContext viewContext = new ViewContext(context, View, ViewData, TempData, writer);
+ View.Render(viewContext, writer);
+
+ if (result != null)
+ {
+ result.ViewEngine.ReleaseView(context, View);
+ }
+ }
+
+ protected abstract ViewEngineResult FindView(ControllerContext context);
+ }
+}
diff --git a/src/System.Web.Mvc/ViewStartPage.cs b/src/System.Web.Mvc/ViewStartPage.cs
new file mode 100644
index 00000000..437943c1
--- /dev/null
+++ b/src/System.Web.Mvc/ViewStartPage.cs
@@ -0,0 +1,43 @@
+using System.Web.Mvc.Properties;
+using System.Web.WebPages;
+
+namespace System.Web.Mvc
+{
+ public abstract class ViewStartPage : StartPage, IViewStartPageChild
+ {
+ private IViewStartPageChild _viewStartPageChild;
+
+ public HtmlHelper<object> Html
+ {
+ get { return ViewStartPageChild.Html; }
+ }
+
+ public UrlHelper Url
+ {
+ get { return ViewStartPageChild.Url; }
+ }
+
+ public ViewContext ViewContext
+ {
+ get { return ViewStartPageChild.ViewContext; }
+ }
+
+ internal IViewStartPageChild ViewStartPageChild
+ {
+ get
+ {
+ if (_viewStartPageChild == null)
+ {
+ IViewStartPageChild child = ChildPage as IViewStartPageChild;
+ if (child == null)
+ {
+ throw new InvalidOperationException(MvcResources.ViewStartPage_RequiresMvcRazorView);
+ }
+ _viewStartPageChild = child;
+ }
+
+ return _viewStartPageChild;
+ }
+ }
+ }
+}
diff --git a/src/System.Web.Mvc/ViewTemplateUserControl.cs b/src/System.Web.Mvc/ViewTemplateUserControl.cs
new file mode 100644
index 00000000..fa6f0096
--- /dev/null
+++ b/src/System.Web.Mvc/ViewTemplateUserControl.cs
@@ -0,0 +1,6 @@
+namespace System.Web.Mvc
+{
+ public class ViewTemplateUserControl : ViewTemplateUserControl<object>
+ {
+ }
+}
diff --git a/src/System.Web.Mvc/ViewTemplateUserControl`1.cs b/src/System.Web.Mvc/ViewTemplateUserControl`1.cs
new file mode 100644
index 00000000..5f3243fd
--- /dev/null
+++ b/src/System.Web.Mvc/ViewTemplateUserControl`1.cs
@@ -0,0 +1,10 @@
+namespace System.Web.Mvc
+{
+ public class ViewTemplateUserControl<TModel> : ViewUserControl<TModel>
+ {
+ protected string FormattedModelValue
+ {
+ get { return ViewData.TemplateInfo.FormattedModelValue.ToString(); }
+ }
+ }
+}
diff --git a/src/System.Web.Mvc/ViewType.cs b/src/System.Web.Mvc/ViewType.cs
new file mode 100644
index 00000000..2bf9c01a
--- /dev/null
+++ b/src/System.Web.Mvc/ViewType.cs
@@ -0,0 +1,19 @@
+using System.ComponentModel;
+using System.Web.UI;
+
+namespace System.Web.Mvc
+{
+ [ControlBuilder(typeof(ViewTypeControlBuilder))]
+ [NonVisualControl]
+ public class ViewType : Control
+ {
+ private string _typeName;
+
+ [DefaultValue("")]
+ public string TypeName
+ {
+ get { return _typeName ?? String.Empty; }
+ set { _typeName = value; }
+ }
+ }
+}
diff --git a/src/System.Web.Mvc/ViewTypeControlBuilder.cs b/src/System.Web.Mvc/ViewTypeControlBuilder.cs
new file mode 100644
index 00000000..01a0a2aa
--- /dev/null
+++ b/src/System.Web.Mvc/ViewTypeControlBuilder.cs
@@ -0,0 +1,29 @@
+using System.CodeDom;
+using System.Collections;
+using System.Web.UI;
+
+namespace System.Web.Mvc
+{
+ internal sealed class ViewTypeControlBuilder : ControlBuilder
+ {
+ private string _typeName;
+
+ public override void Init(TemplateParser parser, ControlBuilder parentBuilder, Type type, string tagName, string id, IDictionary attribs)
+ {
+ base.Init(parser, parentBuilder, type, tagName, id, attribs);
+
+ _typeName = (string)attribs["typename"];
+ }
+
+ public override void ProcessGeneratedCode(
+ CodeCompileUnit codeCompileUnit,
+ CodeTypeDeclaration baseType,
+ CodeTypeDeclaration derivedType,
+ CodeMemberMethod buildMethod,
+ CodeMemberMethod dataBindingMethod)
+ {
+ // Override the view's base type with the explicit base type
+ derivedType.BaseTypes[0] = new CodeTypeReference(_typeName);
+ }
+ }
+}
diff --git a/src/System.Web.Mvc/ViewTypeParserFilter.cs b/src/System.Web.Mvc/ViewTypeParserFilter.cs
new file mode 100644
index 00000000..3d1caecf
--- /dev/null
+++ b/src/System.Web.Mvc/ViewTypeParserFilter.cs
@@ -0,0 +1,109 @@
+using System.Collections;
+using System.Collections.Generic;
+using System.Diagnostics.CodeAnalysis;
+using System.Web.UI;
+
+namespace System.Web.Mvc
+{
+ internal class ViewTypeParserFilter : PageParserFilter
+ {
+ private static Dictionary<string, Type> _directiveBaseTypeMappings = new Dictionary<string, Type>
+ {
+ { "page", typeof(ViewPage) },
+ { "control", typeof(ViewUserControl) },
+ { "master", typeof(ViewMasterPage) },
+ };
+
+ private string _inherits;
+
+ [SuppressMessage("Microsoft.Security", "CA2141:TransparentMethodsMustNotSatisfyLinkDemandsFxCopRule", Justification = "System.Web.Mvc is SecurityTransparent and requires medium trust to run, so this downstream link demand is fine")]
+ public ViewTypeParserFilter()
+ {
+ }
+
+ public override bool AllowCode
+ {
+ get { return true; }
+ }
+
+ public override int NumberOfControlsAllowed
+ {
+ get { return -1; }
+ }
+
+ public override int NumberOfDirectDependenciesAllowed
+ {
+ get { return -1; }
+ }
+
+ public override int TotalNumberOfDependenciesAllowed
+ {
+ get { return -1; }
+ }
+
+ [SuppressMessage("Microsoft.Security", "CA2141:TransparentMethodsMustNotSatisfyLinkDemandsFxCopRule", Justification = "System.Web.Mvc is SecurityTransparent and requires medium trust to run, so this downstream link demand is fine")]
+ public override void PreprocessDirective(string directiveName, IDictionary attributes)
+ {
+ base.PreprocessDirective(directiveName, attributes);
+
+ Type baseType;
+ if (_directiveBaseTypeMappings.TryGetValue(directiveName, out baseType))
+ {
+ string inheritsAttribute = attributes["inherits"] as string;
+
+ // Since the ASP.NET page parser doesn't understand native generic syntax, we
+ // need to swap out whatever the user provided with the default base type for
+ // the given directive (page vs. control vs. master). We stash the old value
+ // and swap it back in inside the control builder. Our "is this generic?"
+ // check here really only works for C# and VB.NET, since we're checking for
+ // < or ( in the type name.
+ //
+ // We only change generic directives, because doing so breaks back-compat
+ // for property setters on @Page, @Control, and @Master directives. The user
+ // can work around this breaking behavior by using a non-generic inherits
+ // directive, or by using the CLR syntax for generic type names.
+
+ if (inheritsAttribute != null && inheritsAttribute.IndexOfAny(new[] { '<', '(' }) > 0)
+ {
+ attributes["inherits"] = baseType.FullName;
+ _inherits = inheritsAttribute;
+ }
+ }
+ }
+
+ [SuppressMessage("Microsoft.Security", "CA2141:TransparentMethodsMustNotSatisfyLinkDemandsFxCopRule", Justification = "System.Web.Mvc is SecurityTransparent and requires medium trust to run, so this downstream link demand is fine")]
+ public override void ParseComplete(ControlBuilder rootBuilder)
+ {
+ base.ParseComplete(rootBuilder);
+
+ IMvcControlBuilder builder = rootBuilder as IMvcControlBuilder;
+ if (builder != null)
+ {
+ builder.Inherits = _inherits;
+ }
+ }
+
+ // Everything else in this class is unrelated to our 'inherits' handling.
+ // Since PageParserFilter blocks everything by default, we need to unblock it
+
+ public override bool AllowBaseType(Type baseType)
+ {
+ return true;
+ }
+
+ public override bool AllowControl(Type controlType, ControlBuilder builder)
+ {
+ return true;
+ }
+
+ public override bool AllowVirtualReference(string referenceVirtualPath, VirtualReferenceType referenceType)
+ {
+ return true;
+ }
+
+ public override bool AllowServerSideInclude(string includeVirtualPath)
+ {
+ return true;
+ }
+ }
+}
diff --git a/src/System.Web.Mvc/ViewUserControl.cs b/src/System.Web.Mvc/ViewUserControl.cs
new file mode 100644
index 00000000..a5f7e075
--- /dev/null
+++ b/src/System.Web.Mvc/ViewUserControl.cs
@@ -0,0 +1,213 @@
+using System.ComponentModel;
+using System.Diagnostics.CodeAnalysis;
+using System.Globalization;
+using System.IO;
+using System.Web.Mvc.Properties;
+using System.Web.UI;
+
+namespace System.Web.Mvc
+{
+ [FileLevelControlBuilder(typeof(ViewUserControlControlBuilder))]
+ public class ViewUserControl : UserControl, IViewDataContainer
+ {
+ private AjaxHelper<object> _ajaxHelper;
+ private DynamicViewDataDictionary _dynamicViewData;
+ private HtmlHelper<object> _htmlHelper;
+ private ViewContext _viewContext;
+ private ViewDataDictionary _viewData;
+ private string _viewDataKey;
+
+ public AjaxHelper<object> Ajax
+ {
+ get
+ {
+ if (_ajaxHelper == null)
+ {
+ _ajaxHelper = new AjaxHelper<object>(ViewContext, this);
+ }
+ return _ajaxHelper;
+ }
+ }
+
+ public HtmlHelper<object> Html
+ {
+ get
+ {
+ if (_htmlHelper == null)
+ {
+ _htmlHelper = new HtmlHelper<object>(ViewContext, this);
+ }
+ return _htmlHelper;
+ }
+ }
+
+ public object Model
+ {
+ get { return ViewData.Model; }
+ }
+
+ public TempDataDictionary TempData
+ {
+ get { return ViewPage.TempData; }
+ }
+
+ public UrlHelper Url
+ {
+ get { return ViewPage.Url; }
+ }
+
+ public dynamic ViewBag
+ {
+ get
+ {
+ if (_dynamicViewData == null)
+ {
+ _dynamicViewData = new DynamicViewDataDictionary(() => ViewData);
+ }
+ return _dynamicViewData;
+ }
+ }
+
+ [Browsable(false)]
+ [DesignerSerializationVisibility(DesignerSerializationVisibility.Hidden)]
+ public ViewContext ViewContext
+ {
+ get { return _viewContext ?? ViewPage.ViewContext; }
+ set { _viewContext = value; }
+ }
+
+ [SuppressMessage("Microsoft.Usage", "CA2227:CollectionPropertiesShouldBeReadOnly", Justification = "This is the mechanism by which the ViewUserControl gets its ViewDataDictionary object.")]
+ [Browsable(false)]
+ [DesignerSerializationVisibility(DesignerSerializationVisibility.Hidden)]
+ public ViewDataDictionary ViewData
+ {
+ get
+ {
+ EnsureViewData();
+ return _viewData;
+ }
+ set { SetViewData(value); }
+ }
+
+ [DefaultValue("")]
+ public string ViewDataKey
+ {
+ get { return _viewDataKey ?? String.Empty; }
+ set { _viewDataKey = value; }
+ }
+
+ internal ViewPage ViewPage
+ {
+ get
+ {
+ ViewPage viewPage = Page as ViewPage;
+ if (viewPage == null)
+ {
+ throw new InvalidOperationException(MvcResources.ViewUserControl_RequiresViewPage);
+ }
+ return viewPage;
+ }
+ }
+
+ public HtmlTextWriter Writer
+ {
+ get { return ViewPage.Writer; }
+ }
+
+ protected virtual void SetViewData(ViewDataDictionary viewData)
+ {
+ _viewData = viewData;
+ }
+
+ protected void EnsureViewData()
+ {
+ if (_viewData != null)
+ {
+ return;
+ }
+
+ // Get the ViewData for this ViewUserControl, optionally using the specified ViewDataKey
+ IViewDataContainer vdc = GetViewDataContainer(this);
+ if (vdc == null)
+ {
+ throw new InvalidOperationException(
+ String.Format(
+ CultureInfo.CurrentCulture,
+ MvcResources.ViewUserControl_RequiresViewDataProvider,
+ AppRelativeVirtualPath));
+ }
+
+ ViewDataDictionary myViewData = vdc.ViewData;
+
+ // If we have a ViewDataKey, try to extract the ViewData from the dictionary, otherwise
+ // return the container's ViewData.
+ if (!String.IsNullOrEmpty(ViewDataKey))
+ {
+ object target = myViewData.Eval(ViewDataKey);
+ myViewData = target as ViewDataDictionary ?? new ViewDataDictionary(myViewData) { Model = target };
+ }
+
+ SetViewData(myViewData);
+ }
+
+ private static IViewDataContainer GetViewDataContainer(Control control)
+ {
+ // Walk up the control hierarchy until we find someone that implements IViewDataContainer
+ while (control != null)
+ {
+ control = control.Parent;
+ IViewDataContainer vdc = control as IViewDataContainer;
+ if (vdc != null)
+ {
+ return vdc;
+ }
+ }
+ return null;
+ }
+
+ public virtual void RenderView(ViewContext viewContext)
+ {
+ using (ViewUserControlContainerPage containerPage = new ViewUserControlContainerPage(this))
+ {
+ RenderViewAndRestoreContentType(containerPage, viewContext);
+ }
+ }
+
+ internal static void RenderViewAndRestoreContentType(ViewPage containerPage, ViewContext viewContext)
+ {
+ // We need to restore the Content-Type since Page.SetIntrinsics() will reset it. It's not possible
+ // to work around the call to SetIntrinsics() since the control's render method requires the
+ // containing page's Response property to be non-null, and SetIntrinsics() is the only way to set
+ // this.
+ string savedContentType = viewContext.HttpContext.Response.ContentType;
+ containerPage.RenderView(viewContext);
+ viewContext.HttpContext.Response.ContentType = savedContentType;
+ }
+
+ [SuppressMessage("Microsoft.Usage", "CA1801:ReviewUnusedParameters", MessageId = "textWriter", Justification = "This method existed in MVC 1.0 and has been deprecated.")]
+ [SuppressMessage("Microsoft.Performance", "CA1822:MarkMembersAsStatic", Justification = "This method existed in MVC 1.0 and has been deprecated.")]
+ [Obsolete("The TextWriter is now provided by the ViewContext object passed to the RenderView method.", true /* error */)]
+ public void SetTextWriter(TextWriter textWriter)
+ {
+ // this is now a no-op
+ }
+
+ private sealed class ViewUserControlContainerPage : ViewPage
+ {
+ private readonly ViewUserControl _userControl;
+
+ public ViewUserControlContainerPage(ViewUserControl userControl)
+ {
+ _userControl = userControl;
+ }
+
+ public override void ProcessRequest(HttpContext context)
+ {
+ _userControl.ID = NextId();
+ Controls.Add(_userControl);
+
+ base.ProcessRequest(context);
+ }
+ }
+ }
+}
diff --git a/src/System.Web.Mvc/ViewUserControlControlBuilder.cs b/src/System.Web.Mvc/ViewUserControlControlBuilder.cs
new file mode 100644
index 00000000..e09e2982
--- /dev/null
+++ b/src/System.Web.Mvc/ViewUserControlControlBuilder.cs
@@ -0,0 +1,18 @@
+using System.CodeDom;
+using System.Web.UI;
+
+namespace System.Web.Mvc
+{
+ internal sealed class ViewUserControlControlBuilder : FileLevelUserControlBuilder, IMvcControlBuilder
+ {
+ public string Inherits { get; set; }
+
+ public override void ProcessGeneratedCode(CodeCompileUnit codeCompileUnit, CodeTypeDeclaration baseType, CodeTypeDeclaration derivedType, CodeMemberMethod buildMethod, CodeMemberMethod dataBindingMethod)
+ {
+ if (!String.IsNullOrWhiteSpace(Inherits))
+ {
+ derivedType.BaseTypes[0] = new CodeTypeReference(Inherits);
+ }
+ }
+ }
+}
diff --git a/src/System.Web.Mvc/ViewUserControl`1.cs b/src/System.Web.Mvc/ViewUserControl`1.cs
new file mode 100644
index 00000000..357888f4
--- /dev/null
+++ b/src/System.Web.Mvc/ViewUserControl`1.cs
@@ -0,0 +1,58 @@
+using System.Diagnostics.CodeAnalysis;
+
+namespace System.Web.Mvc
+{
+ public class ViewUserControl<TModel> : ViewUserControl
+ {
+ private AjaxHelper<TModel> _ajaxHelper;
+ private HtmlHelper<TModel> _htmlHelper;
+ private ViewDataDictionary<TModel> _viewData;
+
+ public new AjaxHelper<TModel> Ajax
+ {
+ get
+ {
+ if (_ajaxHelper == null)
+ {
+ _ajaxHelper = new AjaxHelper<TModel>(ViewContext, this);
+ }
+ return _ajaxHelper;
+ }
+ }
+
+ public new HtmlHelper<TModel> Html
+ {
+ get
+ {
+ if (_htmlHelper == null)
+ {
+ _htmlHelper = new HtmlHelper<TModel>(ViewContext, this);
+ }
+ return _htmlHelper;
+ }
+ }
+
+ public new TModel Model
+ {
+ get { return ViewData.Model; }
+ }
+
+ [SuppressMessage("Microsoft.Usage", "CA2227:CollectionPropertiesShouldBeReadOnly", Justification = "This is settable for unit testing purposes")]
+ public new ViewDataDictionary<TModel> ViewData
+ {
+ get
+ {
+ EnsureViewData();
+ return _viewData;
+ }
+ set { SetViewData(value); }
+ }
+
+ protected override void SetViewData(ViewDataDictionary viewData)
+ {
+ _viewData = new ViewDataDictionary<TModel>(viewData);
+
+ base.SetViewData(_viewData);
+ }
+ }
+}
diff --git a/src/System.Web.Mvc/VirtualPathProviderViewEngine.cs b/src/System.Web.Mvc/VirtualPathProviderViewEngine.cs
new file mode 100644
index 00000000..9cf5d4d4
--- /dev/null
+++ b/src/System.Web.Mvc/VirtualPathProviderViewEngine.cs
@@ -0,0 +1,345 @@
+using System.Collections.Generic;
+using System.Diagnostics.CodeAnalysis;
+using System.Globalization;
+using System.Linq;
+using System.Web.Hosting;
+using System.Web.Mvc.Properties;
+using System.Web.WebPages;
+
+namespace System.Web.Mvc
+{
+ public abstract class VirtualPathProviderViewEngine : IViewEngine
+ {
+ // format is ":ViewCacheEntry:{cacheType}:{prefix}:{name}:{controllerName}:{areaName}:"
+ private const string CacheKeyFormat = ":ViewCacheEntry:{0}:{1}:{2}:{3}:{4}:";
+ private const string CacheKeyPrefixMaster = "Master";
+ private const string CacheKeyPrefixPartial = "Partial";
+ private const string CacheKeyPrefixView = "View";
+ private static readonly string[] _emptyLocations = new string[0];
+ private DisplayModeProvider _displayModeProvider;
+
+ private VirtualPathProvider _vpp;
+ internal Func<string, string> GetExtensionThunk = VirtualPathUtility.GetExtension;
+
+ protected VirtualPathProviderViewEngine()
+ {
+ if (HttpContext.Current == null || HttpContext.Current.IsDebuggingEnabled)
+ {
+ ViewLocationCache = DefaultViewLocationCache.Null;
+ }
+ else
+ {
+ ViewLocationCache = new DefaultViewLocationCache();
+ }
+ }
+
+ [SuppressMessage("Microsoft.Performance", "CA1819:PropertiesShouldNotReturnArrays", Justification = "This is a shipped API")]
+ public string[] AreaMasterLocationFormats { get; set; }
+
+ [SuppressMessage("Microsoft.Performance", "CA1819:PropertiesShouldNotReturnArrays", Justification = "This is a shipped API")]
+ public string[] AreaPartialViewLocationFormats { get; set; }
+
+ [SuppressMessage("Microsoft.Performance", "CA1819:PropertiesShouldNotReturnArrays", Justification = "This is a shipped API")]
+ public string[] AreaViewLocationFormats { get; set; }
+
+ [SuppressMessage("Microsoft.Performance", "CA1819:PropertiesShouldNotReturnArrays", Justification = "This is a shipped API")]
+ public string[] FileExtensions { get; set; }
+
+ [SuppressMessage("Microsoft.Performance", "CA1819:PropertiesShouldNotReturnArrays", Justification = "This is a shipped API")]
+ public string[] MasterLocationFormats { get; set; }
+
+ [SuppressMessage("Microsoft.Performance", "CA1819:PropertiesShouldNotReturnArrays", Justification = "This is a shipped API")]
+ public string[] PartialViewLocationFormats { get; set; }
+
+ public IViewLocationCache ViewLocationCache { get; set; }
+
+ [SuppressMessage("Microsoft.Performance", "CA1819:PropertiesShouldNotReturnArrays", Justification = "This is a shipped API")]
+ public string[] ViewLocationFormats { get; set; }
+
+ protected VirtualPathProvider VirtualPathProvider
+ {
+ get
+ {
+ if (_vpp == null)
+ {
+ _vpp = HostingEnvironment.VirtualPathProvider;
+ }
+ return _vpp;
+ }
+ set { _vpp = value; }
+ }
+
+ protected internal DisplayModeProvider DisplayModeProvider
+ {
+ get { return _displayModeProvider ?? DisplayModeProvider.Instance; }
+ set { _displayModeProvider = value; }
+ }
+
+ private string CreateCacheKey(string prefix, string name, string controllerName, string areaName)
+ {
+ return String.Format(CultureInfo.InvariantCulture, CacheKeyFormat,
+ GetType().AssemblyQualifiedName, prefix, name, controllerName, areaName);
+ }
+
+ internal static string AppendDisplayModeToCacheKey(string cacheKey, string displayMode)
+ {
+ // key format is ":ViewCacheEntry:{cacheType}:{prefix}:{name}:{controllerName}:{areaName}:"
+ // so append "{displayMode}:" to the key
+ return cacheKey + displayMode + ":";
+ }
+
+ protected abstract IView CreatePartialView(ControllerContext controllerContext, string partialPath);
+
+ protected abstract IView CreateView(ControllerContext controllerContext, string viewPath, string masterPath);
+
+ protected virtual bool FileExists(ControllerContext controllerContext, string virtualPath)
+ {
+ return VirtualPathProvider.FileExists(virtualPath);
+ }
+
+ public virtual ViewEngineResult FindPartialView(ControllerContext controllerContext, string partialViewName, bool useCache)
+ {
+ if (controllerContext == null)
+ {
+ throw new ArgumentNullException("controllerContext");
+ }
+ if (String.IsNullOrEmpty(partialViewName))
+ {
+ throw new ArgumentException(MvcResources.Common_NullOrEmpty, "partialViewName");
+ }
+
+ string[] searched;
+ string controllerName = controllerContext.RouteData.GetRequiredString("controller");
+ string partialPath = GetPath(controllerContext, PartialViewLocationFormats, AreaPartialViewLocationFormats, "PartialViewLocationFormats", partialViewName, controllerName, CacheKeyPrefixPartial, useCache, out searched);
+
+ if (String.IsNullOrEmpty(partialPath))
+ {
+ return new ViewEngineResult(searched);
+ }
+
+ return new ViewEngineResult(CreatePartialView(controllerContext, partialPath), this);
+ }
+
+ public virtual ViewEngineResult FindView(ControllerContext controllerContext, string viewName, string masterName, bool useCache)
+ {
+ if (controllerContext == null)
+ {
+ throw new ArgumentNullException("controllerContext");
+ }
+ if (String.IsNullOrEmpty(viewName))
+ {
+ throw new ArgumentException(MvcResources.Common_NullOrEmpty, "viewName");
+ }
+
+ string[] viewLocationsSearched;
+ string[] masterLocationsSearched;
+
+ string controllerName = controllerContext.RouteData.GetRequiredString("controller");
+ string viewPath = GetPath(controllerContext, ViewLocationFormats, AreaViewLocationFormats, "ViewLocationFormats", viewName, controllerName, CacheKeyPrefixView, useCache, out viewLocationsSearched);
+ string masterPath = GetPath(controllerContext, MasterLocationFormats, AreaMasterLocationFormats, "MasterLocationFormats", masterName, controllerName, CacheKeyPrefixMaster, useCache, out masterLocationsSearched);
+
+ if (String.IsNullOrEmpty(viewPath) || (String.IsNullOrEmpty(masterPath) && !String.IsNullOrEmpty(masterName)))
+ {
+ return new ViewEngineResult(viewLocationsSearched.Union(masterLocationsSearched));
+ }
+
+ return new ViewEngineResult(CreateView(controllerContext, viewPath, masterPath), this);
+ }
+
+ private string GetPath(ControllerContext controllerContext, string[] locations, string[] areaLocations, string locationsPropertyName, string name, string controllerName, string cacheKeyPrefix, bool useCache, out string[] searchedLocations)
+ {
+ searchedLocations = _emptyLocations;
+
+ if (String.IsNullOrEmpty(name))
+ {
+ return String.Empty;
+ }
+
+ string areaName = AreaHelpers.GetAreaName(controllerContext.RouteData);
+ bool usingAreas = !String.IsNullOrEmpty(areaName);
+ List<ViewLocation> viewLocations = GetViewLocations(locations, (usingAreas) ? areaLocations : null);
+
+ if (viewLocations.Count == 0)
+ {
+ throw new InvalidOperationException(String.Format(CultureInfo.CurrentCulture,
+ MvcResources.Common_PropertyCannotBeNullOrEmpty, locationsPropertyName));
+ }
+
+ bool nameRepresentsPath = IsSpecificPath(name);
+ string cacheKey = CreateCacheKey(cacheKeyPrefix, name, (nameRepresentsPath) ? String.Empty : controllerName, areaName);
+
+ if (useCache)
+ {
+ // Only look at cached display modes that can handle the context.
+ IEnumerable<IDisplayMode> possibleDisplayModes = DisplayModeProvider.GetAvailableDisplayModesForContext(controllerContext.HttpContext, controllerContext.DisplayMode);
+ foreach (IDisplayMode displayMode in possibleDisplayModes)
+ {
+ string cachedLocation = ViewLocationCache.GetViewLocation(controllerContext.HttpContext, AppendDisplayModeToCacheKey(cacheKey, displayMode.DisplayModeId));
+
+ if (cachedLocation != null)
+ {
+ if (controllerContext.DisplayMode == null)
+ {
+ controllerContext.DisplayMode = displayMode;
+ }
+
+ return cachedLocation;
+ }
+ }
+
+ // GetPath is called again without using the cache.
+ return null;
+ }
+ else
+ {
+ return nameRepresentsPath
+ ? GetPathFromSpecificName(controllerContext, name, cacheKey, ref searchedLocations)
+ : GetPathFromGeneralName(controllerContext, viewLocations, name, controllerName, areaName, cacheKey, ref searchedLocations);
+ }
+ }
+
+ private string GetPathFromGeneralName(ControllerContext controllerContext, List<ViewLocation> locations, string name, string controllerName, string areaName, string cacheKey, ref string[] searchedLocations)
+ {
+ string result = String.Empty;
+ searchedLocations = new string[locations.Count];
+
+ for (int i = 0; i < locations.Count; i++)
+ {
+ ViewLocation location = locations[i];
+ string virtualPath = location.Format(name, controllerName, areaName);
+ DisplayInfo virtualPathDisplayInfo = DisplayModeProvider.GetDisplayInfoForVirtualPath(virtualPath, controllerContext.HttpContext, path => FileExists(controllerContext, path), controllerContext.DisplayMode);
+
+ if (virtualPathDisplayInfo != null)
+ {
+ string resolvedVirtualPath = virtualPathDisplayInfo.FilePath;
+
+ searchedLocations = _emptyLocations;
+ result = resolvedVirtualPath;
+ ViewLocationCache.InsertViewLocation(controllerContext.HttpContext, AppendDisplayModeToCacheKey(cacheKey, virtualPathDisplayInfo.DisplayMode.DisplayModeId), result);
+
+ if (controllerContext.DisplayMode == null)
+ {
+ controllerContext.DisplayMode = virtualPathDisplayInfo.DisplayMode;
+ }
+
+ // Populate the cache with the existing paths returned by all display modes.
+ // Since we currently don't keep track of cache misses, if we cache view.aspx on a request from a standard browser
+ // we don't want a cache hit for view.aspx from a mobile browser so we populate the cache with view.Mobile.aspx.
+ IEnumerable<IDisplayMode> allDisplayModes = DisplayModeProvider.Modes;
+ foreach (IDisplayMode displayMode in allDisplayModes)
+ {
+ if (displayMode.DisplayModeId != virtualPathDisplayInfo.DisplayMode.DisplayModeId)
+ {
+ DisplayInfo displayInfoToCache = displayMode.GetDisplayInfo(controllerContext.HttpContext, virtualPath, virtualPathExists: path => FileExists(controllerContext, path));
+
+ if (displayInfoToCache != null && displayInfoToCache.FilePath != null)
+ {
+ ViewLocationCache.InsertViewLocation(controllerContext.HttpContext, AppendDisplayModeToCacheKey(cacheKey, displayInfoToCache.DisplayMode.DisplayModeId), displayInfoToCache.FilePath);
+ }
+ }
+ }
+ break;
+ }
+
+ searchedLocations[i] = virtualPath;
+ }
+
+ return result;
+ }
+
+ private string GetPathFromSpecificName(ControllerContext controllerContext, string name, string cacheKey, ref string[] searchedLocations)
+ {
+ string result = name;
+
+ if (!(FilePathIsSupported(name) && FileExists(controllerContext, name)))
+ {
+ result = String.Empty;
+ searchedLocations = new[] { name };
+ }
+
+ ViewLocationCache.InsertViewLocation(controllerContext.HttpContext, cacheKey, result);
+ return result;
+ }
+
+ private bool FilePathIsSupported(string virtualPath)
+ {
+ if (FileExtensions == null)
+ {
+ // legacy behavior for custom ViewEngine that might not set the FileExtensions property
+ return true;
+ }
+ else
+ {
+ // get rid of the '.' because the FileExtensions property expects extensions withouth a dot.
+ string extension = GetExtensionThunk(virtualPath).TrimStart('.');
+ return FileExtensions.Contains(extension, StringComparer.OrdinalIgnoreCase);
+ }
+ }
+
+ private static List<ViewLocation> GetViewLocations(string[] viewLocationFormats, string[] areaViewLocationFormats)
+ {
+ List<ViewLocation> allLocations = new List<ViewLocation>();
+
+ if (areaViewLocationFormats != null)
+ {
+ foreach (string areaViewLocationFormat in areaViewLocationFormats)
+ {
+ allLocations.Add(new AreaAwareViewLocation(areaViewLocationFormat));
+ }
+ }
+
+ if (viewLocationFormats != null)
+ {
+ foreach (string viewLocationFormat in viewLocationFormats)
+ {
+ allLocations.Add(new ViewLocation(viewLocationFormat));
+ }
+ }
+
+ return allLocations;
+ }
+
+ private static bool IsSpecificPath(string name)
+ {
+ char c = name[0];
+ return (c == '~' || c == '/');
+ }
+
+ public virtual void ReleaseView(ControllerContext controllerContext, IView view)
+ {
+ IDisposable disposable = view as IDisposable;
+ if (disposable != null)
+ {
+ disposable.Dispose();
+ }
+ }
+
+ private class AreaAwareViewLocation : ViewLocation
+ {
+ public AreaAwareViewLocation(string virtualPathFormatString)
+ : base(virtualPathFormatString)
+ {
+ }
+
+ public override string Format(string viewName, string controllerName, string areaName)
+ {
+ return String.Format(CultureInfo.InvariantCulture, _virtualPathFormatString, viewName, controllerName, areaName);
+ }
+ }
+
+ private class ViewLocation
+ {
+ protected string _virtualPathFormatString;
+
+ public ViewLocation(string virtualPathFormatString)
+ {
+ _virtualPathFormatString = virtualPathFormatString;
+ }
+
+ public virtual string Format(string viewName, string controllerName, string areaName)
+ {
+ return String.Format(CultureInfo.InvariantCulture, _virtualPathFormatString, viewName, controllerName);
+ }
+ }
+ }
+}
diff --git a/src/System.Web.Mvc/WebFormView.cs b/src/System.Web.Mvc/WebFormView.cs
new file mode 100644
index 00000000..d8651656
--- /dev/null
+++ b/src/System.Web.Mvc/WebFormView.cs
@@ -0,0 +1,72 @@
+using System.Globalization;
+using System.IO;
+using System.Web.Mvc.Properties;
+
+namespace System.Web.Mvc
+{
+ public class WebFormView : BuildManagerCompiledView
+ {
+ public WebFormView(ControllerContext controllerContext, string viewPath)
+ : this(controllerContext, viewPath, null, null)
+ {
+ }
+
+ public WebFormView(ControllerContext controllerContext, string viewPath, string masterPath)
+ : this(controllerContext, viewPath, masterPath, null)
+ {
+ }
+
+ public WebFormView(ControllerContext controllerContext, string viewPath, string masterPath, IViewPageActivator viewPageActivator)
+ : base(controllerContext, viewPath, viewPageActivator)
+ {
+ MasterPath = masterPath ?? String.Empty;
+ }
+
+ public string MasterPath { get; private set; }
+
+ protected override void RenderView(ViewContext viewContext, TextWriter writer, object instance)
+ {
+ ViewPage viewPage = instance as ViewPage;
+ if (viewPage != null)
+ {
+ RenderViewPage(viewContext, viewPage);
+ return;
+ }
+
+ ViewUserControl viewUserControl = instance as ViewUserControl;
+ if (viewUserControl != null)
+ {
+ RenderViewUserControl(viewContext, viewUserControl);
+ return;
+ }
+
+ throw new InvalidOperationException(
+ String.Format(
+ CultureInfo.CurrentCulture,
+ MvcResources.WebFormViewEngine_WrongViewBase,
+ ViewPath));
+ }
+
+ private void RenderViewPage(ViewContext context, ViewPage page)
+ {
+ if (!String.IsNullOrEmpty(MasterPath))
+ {
+ page.MasterLocation = MasterPath;
+ }
+
+ page.ViewData = context.ViewData;
+ page.RenderView(context);
+ }
+
+ private void RenderViewUserControl(ViewContext context, ViewUserControl control)
+ {
+ if (!String.IsNullOrEmpty(MasterPath))
+ {
+ throw new InvalidOperationException(MvcResources.WebFormViewEngine_UserControlCannotHaveMaster);
+ }
+
+ control.ViewData = context.ViewData;
+ control.RenderView(context);
+ }
+ }
+}
diff --git a/src/System.Web.Mvc/WebFormViewEngine.cs b/src/System.Web.Mvc/WebFormViewEngine.cs
new file mode 100644
index 00000000..2827e736
--- /dev/null
+++ b/src/System.Web.Mvc/WebFormViewEngine.cs
@@ -0,0 +1,62 @@
+namespace System.Web.Mvc
+{
+ public class WebFormViewEngine : BuildManagerViewEngine
+ {
+ public WebFormViewEngine()
+ : this(null)
+ {
+ }
+
+ public WebFormViewEngine(IViewPageActivator viewPageActivator)
+ : base(viewPageActivator)
+ {
+ MasterLocationFormats = new[]
+ {
+ "~/Views/{1}/{0}.master",
+ "~/Views/Shared/{0}.master"
+ };
+
+ AreaMasterLocationFormats = new[]
+ {
+ "~/Areas/{2}/Views/{1}/{0}.master",
+ "~/Areas/{2}/Views/Shared/{0}.master",
+ };
+
+ ViewLocationFormats = new[]
+ {
+ "~/Views/{1}/{0}.aspx",
+ "~/Views/{1}/{0}.ascx",
+ "~/Views/Shared/{0}.aspx",
+ "~/Views/Shared/{0}.ascx"
+ };
+
+ AreaViewLocationFormats = new[]
+ {
+ "~/Areas/{2}/Views/{1}/{0}.aspx",
+ "~/Areas/{2}/Views/{1}/{0}.ascx",
+ "~/Areas/{2}/Views/Shared/{0}.aspx",
+ "~/Areas/{2}/Views/Shared/{0}.ascx",
+ };
+
+ PartialViewLocationFormats = ViewLocationFormats;
+ AreaPartialViewLocationFormats = AreaViewLocationFormats;
+
+ FileExtensions = new[]
+ {
+ "aspx",
+ "ascx",
+ "master",
+ };
+ }
+
+ protected override IView CreatePartialView(ControllerContext controllerContext, string partialPath)
+ {
+ return new WebFormView(controllerContext, partialPath, null, ViewPageActivator);
+ }
+
+ protected override IView CreateView(ControllerContext controllerContext, string viewPath, string masterPath)
+ {
+ return new WebFormView(controllerContext, viewPath, masterPath, ViewPageActivator);
+ }
+ }
+}
diff --git a/src/System.Web.Mvc/WebViewPage.cs b/src/System.Web.Mvc/WebViewPage.cs
new file mode 100644
index 00000000..3799c602
--- /dev/null
+++ b/src/System.Web.Mvc/WebViewPage.cs
@@ -0,0 +1,115 @@
+using System.Diagnostics.CodeAnalysis;
+using System.Globalization;
+using System.IO;
+using System.Web.Mvc.Properties;
+using System.Web.WebPages;
+
+namespace System.Web.Mvc
+{
+ public abstract class WebViewPage : WebPageBase, IViewDataContainer, IViewStartPageChild
+ {
+ private ViewDataDictionary _viewData;
+ private DynamicViewDataDictionary _dynamicViewData;
+ private HttpContextBase _context;
+
+ public AjaxHelper<object> Ajax { get; set; }
+
+ public override HttpContextBase Context
+ {
+ // REVIEW why are we forced to override this?
+ get { return _context ?? ViewContext.HttpContext; }
+ set { _context = value; }
+ }
+
+ public HtmlHelper<object> Html { get; set; }
+
+ public object Model
+ {
+ get { return ViewData.Model; }
+ }
+
+ internal string OverridenLayoutPath { get; set; }
+
+ public TempDataDictionary TempData
+ {
+ get { return ViewContext.TempData; }
+ }
+
+ public UrlHelper Url { get; set; }
+
+ public dynamic ViewBag
+ {
+ get
+ {
+ if (_dynamicViewData == null)
+ {
+ _dynamicViewData = new DynamicViewDataDictionary(() => ViewData);
+ }
+ return _dynamicViewData;
+ }
+ }
+
+ public ViewContext ViewContext { get; set; }
+
+ [SuppressMessage("Microsoft.Usage", "CA2227:CollectionPropertiesShouldBeReadOnly", Justification = "This is the mechanism by which the ViewPage gets its ViewDataDictionary object.")]
+ public ViewDataDictionary ViewData
+ {
+ get
+ {
+ if (_viewData == null)
+ {
+ SetViewData(new ViewDataDictionary());
+ }
+ return _viewData;
+ }
+ set { SetViewData(value); }
+ }
+
+ protected override void ConfigurePage(WebPageBase parentPage)
+ {
+ var baseViewPage = parentPage as WebViewPage;
+ if (baseViewPage == null)
+ {
+ // TODO : review if this check is even necessary.
+ // When this method is called by the framework parentPage should already be an instance of WebViewPage
+ // Need to review what happens if this method gets called in Plan9 pointing at an MVC view
+ throw new InvalidOperationException(String.Format(CultureInfo.CurrentCulture, MvcResources.CshtmlView_WrongViewBase, parentPage.VirtualPath));
+ }
+
+ // Set ViewContext and ViewData here so that the layout page inherits ViewData from the main page
+ ViewContext = baseViewPage.ViewContext;
+ ViewData = baseViewPage.ViewData;
+ InitHelpers();
+ }
+
+ public override void ExecutePageHierarchy()
+ {
+ // Change the Writer so that things like Html.BeginForm work correctly
+ TextWriter oldWriter = ViewContext.Writer;
+ ViewContext.Writer = Output;
+
+ base.ExecutePageHierarchy();
+
+ // Overwrite LayoutPage so that returning a view with a custom master page works.
+ if (!String.IsNullOrEmpty(OverridenLayoutPath))
+ {
+ Layout = OverridenLayoutPath;
+ }
+
+ // Restore the old View Context Writer
+ ViewContext.Writer = oldWriter;
+ }
+
+ public virtual void InitHelpers()
+ {
+ Ajax = new AjaxHelper<object>(ViewContext, this);
+ Html = new HtmlHelper<object>(ViewContext, this);
+ Url = new UrlHelper(ViewContext.RequestContext);
+ }
+
+ protected virtual void SetViewData(ViewDataDictionary viewData)
+ {
+ _viewData = viewData;
+ }
+ }
+}
diff --git a/src/System.Web.Mvc/WebViewPage`1.cs b/src/System.Web.Mvc/WebViewPage`1.cs
new file mode 100644
index 00000000..1b25f6b4
--- /dev/null
+++ b/src/System.Web.Mvc/WebViewPage`1.cs
@@ -0,0 +1,47 @@
+using System.Diagnostics.CodeAnalysis;
+
+namespace System.Web.Mvc
+{
+ public abstract class WebViewPage<TModel> : WebViewPage
+ {
+ private ViewDataDictionary<TModel> _viewData;
+
+ public new AjaxHelper<TModel> Ajax { get; set; }
+
+ public new HtmlHelper<TModel> Html { get; set; }
+
+ public new TModel Model
+ {
+ get { return ViewData.Model; }
+ }
+
+ [SuppressMessage("Microsoft.Usage", "CA2227:CollectionPropertiesShouldBeReadOnly", Justification = "This is the mechanism by which the ViewPage gets its ViewDataDictionary object.")]
+ public new ViewDataDictionary<TModel> ViewData
+ {
+ get
+ {
+ if (_viewData == null)
+ {
+ SetViewData(new ViewDataDictionary<TModel>());
+ }
+ return _viewData;
+ }
+ set { SetViewData(value); }
+ }
+
+ public override void InitHelpers()
+ {
+ base.InitHelpers();
+
+ Ajax = new AjaxHelper<TModel>(ViewContext, this);
+ Html = new HtmlHelper<TModel>(ViewContext, this);
+ }
+
+ protected override void SetViewData(ViewDataDictionary viewData)
+ {
+ _viewData = new ViewDataDictionary<TModel>(viewData);
+
+ base.SetViewData(_viewData);
+ }
+ }
+}
diff --git a/src/System.Web.Mvc/packages.config b/src/System.Web.Mvc/packages.config
new file mode 100644
index 00000000..f143a04f
--- /dev/null
+++ b/src/System.Web.Mvc/packages.config
@@ -0,0 +1,4 @@
+<?xml version="1.0" encoding="utf-8"?>
+<packages>
+ <package id="Microsoft.Web.Infrastructure" version="1.0.0.0" />
+</packages> \ No newline at end of file