├── .editorconfig ├── .gitattributes ├── .github ├── dependabot.yml └── workflows │ └── on-tag-do-release.yml ├── .gitignore ├── Directory.Build.props ├── ExternalValidation ├── ExternalValidation.csproj ├── ValidationFactory.cs └── ValidationTemplate.cs ├── Tests ├── AssemblyTests │ ├── GenericExternalTests.cs │ └── GenericInternalTests.cs ├── Templates │ └── DataAnnotations │ │ ├── DataAnnotationsTemplateTests.cs │ │ ├── Model.cs │ │ └── ValidationTemplate.cs ├── TemplatesGeneric │ ├── DataAnnotations │ │ ├── DataAnnotationsTemplateTests.cs │ │ ├── Model.cs │ │ └── ValidationTemplate.cs │ └── FluentValidation │ │ ├── FluentTemplateTests.cs │ │ ├── Model.cs │ │ ├── ModelValidator.cs │ │ ├── ValidationFactory.cs │ │ └── ValidationTemplate.cs ├── Tests.csproj └── ValidationTester.cs ├── Validar.Fody ├── CecilExtensions.cs ├── ImplementsInterface.cs ├── Injectors │ ├── DataErrorInfoInjector.cs │ └── NotifyDataErrorInfoInjector.cs ├── InstructionListExtensions.cs ├── ModuleWeaver.cs ├── ReturnFixer.cs ├── TemplateFieldInjector.cs ├── TemplateFinders │ ├── DataErrorInfoFinder.cs │ └── NotifyDataErrorInfoFinder.cs ├── Validar.Fody.csproj └── ValidationTemplateFinder.cs ├── Validar.sln ├── Validar.sln.DotSettings ├── Validar ├── InjectValidationAttribute.cs ├── Validar.csproj ├── ValidationTemplateAttribute.cs └── key.snk ├── WithGenericExternal ├── Annotations.cs ├── AssemblyInfo.cs ├── Model.cs ├── ModelValidator.cs ├── ModelWithImplementation.cs ├── ModelWithImplementationValidator.cs └── WithGenericExternal.csproj ├── WithGenericInternal ├── Annotations.cs ├── Model.cs ├── ModelValidator.cs ├── ModelWithImplementation.cs ├── ModelWithImplementationValidator.cs ├── ValidationFactory.cs ├── ValidationTemplate.cs └── WithGenericInternal.csproj ├── appveyor.yml ├── license.txt ├── package_icon.png └── readme.md /.editorconfig: -------------------------------------------------------------------------------- 1 | # EditorConfig: http://EditorConfig.org 2 | 3 | # top-most EditorConfig file 4 | root = true 5 | 6 | [*] 7 | indent_style = space:error 8 | 9 | [*.cs] 10 | indent_size = 4:error 11 | charset = utf-8-bom:error 12 | 13 | # Sort using and Import directives with System.* appearing first 14 | dotnet_sort_system_directives_first = true:error 15 | 16 | # Avoid "this." and "Me." if not necessary 17 | dotnet_style_qualification_for_field = false:error 18 | dotnet_style_qualification_for_property = false:error 19 | dotnet_style_qualification_for_method = false:error 20 | dotnet_style_qualification_for_event = false:error 21 | 22 | # Use language keywords instead of framework type names for type references 23 | dotnet_style_predefined_type_for_locals_parameters_members = true:error 24 | dotnet_style_predefined_type_for_member_access = true:error 25 | 26 | # Suggest more modern language features when available 27 | dotnet_style_object_initializer = true:suggestion 28 | dotnet_style_collection_initializer = true:suggestion 29 | dotnet_style_coalesce_expression = false:suggestion 30 | dotnet_style_null_propagation = true:suggestion 31 | dotnet_style_explicit_tuple_names = true:suggestion 32 | 33 | # Prefer "var" everywhere 34 | csharp_style_var_for_built_in_types = true:error 35 | csharp_style_var_when_type_is_apparent = true:error 36 | csharp_style_var_elsewhere = true:error 37 | 38 | # Prefer method-like constructs to have a block body 39 | csharp_style_expression_bodied_methods = false:none 40 | csharp_style_expression_bodied_constructors = false:none 41 | csharp_style_expression_bodied_operators = false:none 42 | 43 | # Prefer property-like constructs to have an expression-body 44 | csharp_style_expression_bodied_properties = true:suggestion 45 | csharp_style_expression_bodied_indexers = true:suggestion 46 | csharp_style_expression_bodied_accessors = true:none 47 | 48 | # Suggest more modern language features when available 49 | csharp_style_pattern_matching_over_is_with_cast_check = true:error 50 | csharp_style_pattern_matching_over_as_with_null_check = true:error 51 | csharp_style_inlined_variable_declaration = true:suggestion 52 | csharp_style_throw_expression = true:suggestion 53 | csharp_style_conditional_delegate_call = true:suggestion 54 | 55 | # Newline settings 56 | #csharp_new_line_before_open_brace = all:error 57 | csharp_new_line_before_else = true:error 58 | csharp_new_line_before_catch = true:error 59 | csharp_new_line_before_finally = true:error 60 | csharp_new_line_before_members_in_object_initializers = true:error 61 | csharp_new_line_before_members_in_anonymous_types = true:error 62 | 63 | #braces 64 | #csharp_prefer_braces = true:error 65 | 66 | # msbuild 67 | [*.{csproj,targets,props}] 68 | indent_size = 2 69 | 70 | # Xml files 71 | [*.{xml,config,nuspec,resx,vsixmanifest}] 72 | indent_size = 2 73 | resharper_xml_wrap_tags_and_pi = true:error 74 | 75 | # JSON files 76 | [*.json] 77 | indent_size = 2 78 | -------------------------------------------------------------------------------- /.gitattributes: -------------------------------------------------------------------------------- 1 | # Auto detect text files and perform LF normalization 2 | * text 3 | 4 | # Don't check these into the repo as LF to work around TeamCity bug 5 | *.xml -text 6 | *.targets -text 7 | 8 | # Custom for Visual Studio 9 | *.cs diff=csharp 10 | *.sln merge=union 11 | *.csproj merge=union 12 | *.vbproj merge=union 13 | *.fsproj merge=union 14 | *.dbproj merge=union 15 | 16 | # Denote all files that are truly binary and should not be modified. 17 | *.dll binary 18 | *.exe binary 19 | *.png binary 20 | *.ico binary 21 | *.snk binary 22 | *.pdb binary 23 | -------------------------------------------------------------------------------- /.github/dependabot.yml: -------------------------------------------------------------------------------- 1 | version: 2 2 | updates: 3 | - package-ecosystem: nuget 4 | directory: "/" 5 | schedule: 6 | interval: daily 7 | open-pull-requests-limit: 10 8 | ignore: 9 | - dependency-name: Verify.Xunit 10 | versions: 11 | - 10.9.0 12 | - 11.7.0 13 | - 11.8.0 14 | - dependency-name: FluentValidation 15 | versions: 16 | - 10.0.0 17 | - 10.0.1 18 | - 10.0.3 19 | -------------------------------------------------------------------------------- /.github/workflows/on-tag-do-release.yml: -------------------------------------------------------------------------------- 1 | name: on-tag-do-release 2 | on: 3 | push: 4 | tags: 5 | - '[0-9]+\.[0-9]+\.[0-9]+' 6 | jobs: 7 | release: 8 | runs-on: windows-latest 9 | steps: 10 | - name: Run 11 | run: | 12 | dotnet tool install --global GitReleaseManager.Tool 13 | tag="${GITHUB_REF:10}" 14 | owner="${GITHUB_REPOSITORY%/*}" 15 | repo="${GITHUB_REPOSITORY#*/}" 16 | dotnet-gitreleasemanager create -m ${tag} --token ${{secrets.GITHUB_TOKEN}} -o ${owner} -r ${repo} 17 | dotnet-gitreleasemanager close -m ${tag} --token ${{secrets.GITHUB_TOKEN}} -o ${owner} -r ${repo} 18 | dotnet-gitreleasemanager publish -t ${tag} --token ${{secrets.GITHUB_TOKEN}} -o ${owner} -r ${repo} 19 | shell: bash -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | ## Ignore Visual Studio temporary files, build results, and 2 | ## files generated by popular Visual Studio add-ons. 3 | ## 4 | ## Get latest from https://github.com/github/gitignore/blob/master/VisualStudio.gitignore 5 | 6 | # User-specific files 7 | *.suo 8 | *.user 9 | *.userosscache 10 | *.sln.docstates 11 | 12 | # User-specific files (MonoDevelop/Xamarin Studio) 13 | *.userprefs 14 | 15 | # Build results 16 | [Dd]ebug/ 17 | [Dd]ebugPublic/ 18 | [Rr]elease/ 19 | [Rr]eleases/ 20 | x64/ 21 | x86/ 22 | bld/ 23 | [Bb]in/ 24 | [Oo]bj/ 25 | [Ll]og/ 26 | 27 | # Visual Studio 2015 cache/options directory 28 | .vs/ 29 | # Uncomment if you have tasks that create the project's static files in wwwroot 30 | #wwwroot/ 31 | 32 | # MSTest test Results 33 | [Tt]est[Rr]esult*/ 34 | [Bb]uild[Ll]og.* 35 | 36 | # NUNIT 37 | *.VisualState.xml 38 | TestResult.xml 39 | 40 | # Build Results of an ATL Project 41 | [Dd]ebugPS/ 42 | [Rr]eleasePS/ 43 | dlldata.c 44 | 45 | # Benchmark Results 46 | BenchmarkDotNet.Artifacts/ 47 | 48 | # .NET Core 49 | project.lock.json 50 | project.fragment.lock.json 51 | artifacts/ 52 | **/Properties/launchSettings.json 53 | 54 | *_i.c 55 | *_p.c 56 | *_i.h 57 | *.ilk 58 | *.meta 59 | *.obj 60 | *.pch 61 | *.pdb 62 | *.pgc 63 | *.pgd 64 | *.rsp 65 | *.sbr 66 | *.tlb 67 | *.tli 68 | *.tlh 69 | *.tmp 70 | *.tmp_proj 71 | *.log 72 | *.vspscc 73 | *.vssscc 74 | .builds 75 | *.pidb 76 | *.svclog 77 | *.scc 78 | 79 | # Chutzpah Test files 80 | _Chutzpah* 81 | 82 | # Visual C++ cache files 83 | ipch/ 84 | *.aps 85 | *.ncb 86 | *.opendb 87 | *.opensdf 88 | *.sdf 89 | *.cachefile 90 | *.VC.db 91 | *.VC.VC.opendb 92 | 93 | # Visual Studio profiler 94 | *.psess 95 | *.vsp 96 | *.vspx 97 | *.sap 98 | 99 | # Visual Studio Trace Files 100 | *.e2e 101 | 102 | # TFS 2012 Local Workspace 103 | $tf/ 104 | 105 | # Guidance Automation Toolkit 106 | *.gpState 107 | 108 | # ReSharper is a .NET coding add-in 109 | _ReSharper*/ 110 | *.[Rr]e[Ss]harper 111 | *.DotSettings.user 112 | 113 | # JustCode is a .NET coding add-in 114 | .JustCode 115 | 116 | # TeamCity is a build add-in 117 | _TeamCity* 118 | 119 | # DotCover is a Code Coverage Tool 120 | *.dotCover 121 | 122 | # AxoCover is a Code Coverage Tool 123 | .axoCover/* 124 | !.axoCover/settings.json 125 | 126 | # Visual Studio code coverage results 127 | *.coverage 128 | *.coveragexml 129 | 130 | # NCrunch 131 | _NCrunch_* 132 | .*crunch*.local.xml 133 | nCrunchTemp_* 134 | 135 | # MightyMoose 136 | *.mm.* 137 | AutoTest.Net/ 138 | 139 | # Web workbench (sass) 140 | .sass-cache/ 141 | 142 | # Installshield output folder 143 | [Ee]xpress/ 144 | 145 | # DocProject is a documentation generator add-in 146 | DocProject/buildhelp/ 147 | DocProject/Help/*.HxT 148 | DocProject/Help/*.HxC 149 | DocProject/Help/*.hhc 150 | DocProject/Help/*.hhk 151 | DocProject/Help/*.hhp 152 | DocProject/Help/Html2 153 | DocProject/Help/html 154 | 155 | # Click-Once directory 156 | publish/ 157 | 158 | # Publish Web Output 159 | *.[Pp]ublish.xml 160 | *.azurePubxml 161 | # Note: Comment the next line if you want to checkin your web deploy settings, 162 | # but database connection strings (with potential passwords) will be unencrypted 163 | *.pubxml 164 | *.publishproj 165 | 166 | # Microsoft Azure Web App publish settings. Comment the next line if you want to 167 | # checkin your Azure Web App publish settings, but sensitive information contained 168 | # in these scripts will be unencrypted 169 | PublishScripts/ 170 | 171 | # NuGet Packages 172 | *.nupkg 173 | # The packages folder can be ignored because of Package Restore 174 | **/[Pp]ackages/* 175 | # except build/, which is used as an MSBuild target. 176 | !**/[Pp]ackages/build/ 177 | # Uncomment if necessary however generally it will be regenerated when needed 178 | #!**/[Pp]ackages/repositories.config 179 | # NuGet v3's project.json files produces more ignorable files 180 | *.nuget.props 181 | *.nuget.targets 182 | nugets/ 183 | 184 | # Microsoft Azure Build Output 185 | csx/ 186 | *.build.csdef 187 | 188 | # Microsoft Azure Emulator 189 | ecf/ 190 | rcf/ 191 | 192 | # Windows Store app package directories and files 193 | AppPackages/ 194 | BundleArtifacts/ 195 | Package.StoreAssociation.xml 196 | _pkginfo.txt 197 | *.appx 198 | 199 | # Visual Studio cache files 200 | # files ending in .cache can be ignored 201 | *.[Cc]ache 202 | # but keep track of directories ending in .cache 203 | !*.[Cc]ache/ 204 | 205 | # Others 206 | ClientBin/ 207 | ~$* 208 | *~ 209 | *.dbmdl 210 | *.dbproj.schemaview 211 | *.jfm 212 | *.pfx 213 | *.publishsettings 214 | orleans.codegen.cs 215 | 216 | # Since there are multiple workflows, uncomment next line to ignore bower_components 217 | # (https://github.com/github/gitignore/pull/1529#issuecomment-104372622) 218 | #bower_components/ 219 | 220 | # RIA/Silverlight projects 221 | Generated_Code/ 222 | 223 | # Backup & report files from converting an old project file 224 | # to a newer Visual Studio version. Backup files are not needed, 225 | # because we have git ;-) 226 | _UpgradeReport_Files/ 227 | Backup*/ 228 | UpgradeLog*.XML 229 | UpgradeLog*.htm 230 | 231 | # SQL Server files 232 | *.mdf 233 | *.ldf 234 | *.ndf 235 | 236 | # Business Intelligence projects 237 | *.rdl.data 238 | *.bim.layout 239 | *.bim_*.settings 240 | 241 | # Microsoft Fakes 242 | FakesAssemblies/ 243 | 244 | # GhostDoc plugin setting file 245 | *.GhostDoc.xml 246 | 247 | # Node.js Tools for Visual Studio 248 | .ntvs_analysis.dat 249 | node_modules/ 250 | 251 | # Typescript v1 declaration files 252 | typings/ 253 | 254 | # Visual Studio 6 build log 255 | *.plg 256 | 257 | # Visual Studio 6 workspace options file 258 | *.opt 259 | 260 | # Visual Studio 6 auto-generated workspace file (contains which files were open etc.) 261 | *.vbw 262 | 263 | # Visual Studio LightSwitch build output 264 | **/*.HTMLClient/GeneratedArtifacts 265 | **/*.DesktopClient/GeneratedArtifacts 266 | **/*.DesktopClient/ModelManifest.xml 267 | **/*.Server/GeneratedArtifacts 268 | **/*.Server/ModelManifest.xml 269 | _Pvt_Extensions 270 | 271 | # Paket dependency manager 272 | .paket/paket.exe 273 | paket-files/ 274 | 275 | # FAKE - F# Make 276 | .fake/ 277 | 278 | # JetBrains Rider 279 | .idea/ 280 | *.sln.iml 281 | 282 | # CodeRush 283 | .cr/ 284 | 285 | # Python Tools for Visual Studio (PTVS) 286 | __pycache__/ 287 | *.pyc 288 | 289 | # Cake - Uncomment if you are using it 290 | # tools/** 291 | # !tools/packages.config 292 | 293 | # Tabs Studio 294 | *.tss 295 | 296 | # Telerik's JustMock configuration file 297 | *.jmconfig 298 | 299 | # BizTalk build output 300 | *.btp.cs 301 | *.btm.cs 302 | *.odx.cs 303 | *.xsd.cs 304 | 305 | # OpenCover UI analysis results 306 | OpenCover/ 307 | -------------------------------------------------------------------------------- /Directory.Build.props: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 1.9.0 5 | true 6 | latest 7 | 8 | -------------------------------------------------------------------------------- /ExternalValidation/ExternalValidation.csproj: -------------------------------------------------------------------------------- 1 |  2 | 3 | 4 | net472;netstandard2.0 5 | 6 | 7 | 8 | 9 | -------------------------------------------------------------------------------- /ExternalValidation/ValidationFactory.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | using System.Collections.Concurrent; 3 | using System.ComponentModel; 4 | using FluentValidation; 5 | 6 | namespace Generic 7 | { 8 | public static class ValidationFactory 9 | { 10 | static ConcurrentDictionary validators = new ConcurrentDictionary(); 11 | 12 | public static IValidator GetValidator() 13 | where T : INotifyPropertyChanged 14 | { var modelType = typeof (T); 15 | var modelTypeHandle = modelType.TypeHandle; 16 | if (!validators.TryGetValue(modelTypeHandle, out var validator)) 17 | { 18 | var typeName = $"{modelType.Namespace}.{modelType.Name}Validator"; 19 | var type = modelType.Assembly.GetType(typeName, true); 20 | validators[modelTypeHandle] = validator = (IValidator) Activator.CreateInstance(type); 21 | } 22 | 23 | return (IValidator) validator; 24 | } 25 | } 26 | } -------------------------------------------------------------------------------- /ExternalValidation/ValidationTemplate.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | using System.Collections; 3 | using System.ComponentModel; 4 | using System.Linq; 5 | using FluentValidation; 6 | using FluentValidation.Results; 7 | 8 | namespace Generic 9 | { 10 | public class ValidationTemplate : 11 | IDataErrorInfo, 12 | INotifyDataErrorInfo 13 | where T : INotifyPropertyChanged 14 | { 15 | IValidator validator; 16 | ValidationResult result; 17 | ValidationContext context; 18 | 19 | public ValidationTemplate(T target) 20 | { 21 | validator = ValidationFactory.GetValidator(); 22 | context = new ValidationContext(target); 23 | result = validator.Validate(context); 24 | target.PropertyChanged += Validate; 25 | } 26 | 27 | 28 | void Validate(object sender, PropertyChangedEventArgs e) 29 | { 30 | result = validator.Validate(context); 31 | foreach (var error in result.Errors) 32 | { 33 | RaiseErrorsChanged(error.PropertyName); 34 | } 35 | } 36 | 37 | public IEnumerable GetErrors(string propertyName) 38 | { 39 | return result.Errors 40 | .Where(x => x.PropertyName == propertyName) 41 | .Select(x => x.ErrorMessage); 42 | } 43 | 44 | public bool HasErrors => result.Errors.Count > 0; 45 | 46 | public string Error 47 | { 48 | get 49 | { 50 | var strings = result.Errors.Select(x => x.ErrorMessage) 51 | .ToArray(); 52 | return string.Join(Environment.NewLine, strings); 53 | } 54 | } 55 | 56 | public string this[string propertyName] 57 | { 58 | get 59 | { 60 | var strings = result.Errors.Where(x => x.PropertyName == propertyName) 61 | .Select(x => x.ErrorMessage) 62 | .ToArray(); 63 | return string.Join(Environment.NewLine, strings); 64 | } 65 | } 66 | 67 | public event EventHandler ErrorsChanged; 68 | 69 | void RaiseErrorsChanged(string propertyName) 70 | { 71 | var handler = ErrorsChanged; 72 | handler?.Invoke(this, new DataErrorsChangedEventArgs(propertyName)); 73 | } 74 | } 75 | } -------------------------------------------------------------------------------- /Tests/AssemblyTests/GenericExternalTests.cs: -------------------------------------------------------------------------------- 1 | using System.Linq; 2 | using Fody; 3 | using Xunit; 4 | 5 | public class GenericExternalTests 6 | { 7 | static TestResult testResult; 8 | 9 | static GenericExternalTests() 10 | { 11 | var weavingTask = new ModuleWeaver(); 12 | testResult = weavingTask.ExecuteTestRun("WithGenericExternal.dll", 13 | ignoreCodes: new[] { "0x80131869" }); 14 | } 15 | 16 | [Fact] 17 | public void DataErrorInfo() 18 | { 19 | var instance = testResult.GetInstance("WithGenericExternal.MyModel"); 20 | ValidationTester.TestDataErrorInfo(instance); 21 | } 22 | 23 | [Fact] 24 | public void EnsureReferenceRemoved() 25 | { 26 | var instance = testResult.Assembly.CustomAttributes.FirstOrDefault(x => x.AttributeType.Name == "ValidationTemplateAttribute"); 27 | Assert.Null(instance); 28 | } 29 | 30 | [Fact] 31 | public void DataErrorInfoWithImplementation() 32 | { 33 | var instance = testResult.GetInstance("WithGenericExternal.ModelWithImplementation"); 34 | ValidationTester.TestDataErrorInfo(instance); 35 | } 36 | 37 | [Fact] 38 | public void NotifyDataErrorInfo() 39 | { 40 | var instance = testResult.GetInstance("WithGenericExternal.MyModel"); 41 | ValidationTester.TestNotifyDataErrorInfo(instance); 42 | } 43 | 44 | [Fact] 45 | public void NotifyDataErrorInfoWithImplementation() 46 | { 47 | var instance = testResult.GetInstance("WithGenericExternal.ModelWithImplementation"); 48 | ValidationTester.TestNotifyDataErrorInfo(instance); 49 | } 50 | } -------------------------------------------------------------------------------- /Tests/AssemblyTests/GenericInternalTests.cs: -------------------------------------------------------------------------------- 1 | using Fody; 2 | using Xunit; 3 | 4 | public class GenericInternalTests 5 | { 6 | static TestResult testResult; 7 | 8 | static GenericInternalTests() 9 | { 10 | var weavingTask = new ModuleWeaver(); 11 | testResult = weavingTask.ExecuteTestRun("WithGenericInternal.dll", 12 | ignoreCodes: new[] { "0x80131869" }); 13 | } 14 | 15 | [Fact] 16 | public void DataErrorInfo() 17 | { 18 | var instance = testResult.GetInstance("WithGenericInternal.Model"); 19 | ValidationTester.TestDataErrorInfo(instance); 20 | } 21 | 22 | [Fact] 23 | public void DataErrorInfoWithImplementation() 24 | { 25 | var instance = testResult.GetInstance("WithGenericInternal.ModelWithImplementation"); 26 | ValidationTester.TestDataErrorInfo(instance); 27 | } 28 | 29 | [Fact] 30 | public void NotifyDataErrorInfo() 31 | { 32 | var instance = testResult.GetInstance("WithGenericInternal.Model"); 33 | ValidationTester.TestNotifyDataErrorInfo(instance); 34 | } 35 | 36 | [Fact] 37 | public void NotifyDataErrorInfoWithImplementation() 38 | { 39 | var instance = testResult.GetInstance("WithGenericInternal.ModelWithImplementation"); 40 | ValidationTester.TestNotifyDataErrorInfo(instance); 41 | } 42 | } -------------------------------------------------------------------------------- /Tests/Templates/DataAnnotations/DataAnnotationsTemplateTests.cs: -------------------------------------------------------------------------------- 1 | namespace Templates.DataAnnotations 2 | { 3 | using Xunit; 4 | 5 | public class TemplateTests 6 | { 7 | [Fact] 8 | public void DataErrorInfo() 9 | { 10 | ValidationTester.TestDataErrorInfo(new Model()); 11 | } 12 | 13 | [Fact] 14 | public void NotifyDataErrorInfo() 15 | { 16 | ValidationTester.TestNotifyDataErrorInfo(new Model()); 17 | } 18 | } 19 | } -------------------------------------------------------------------------------- /Tests/Templates/DataAnnotations/Model.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | using System.Collections; 3 | using System.ComponentModel; 4 | using System.ComponentModel.DataAnnotations; 5 | using System.Runtime.CompilerServices; 6 | 7 | namespace Templates.DataAnnotations 8 | { 9 | public class Model : 10 | IDataErrorInfo, 11 | INotifyPropertyChanged, 12 | INotifyDataErrorInfo 13 | { 14 | ValidationTemplate validationTemplate; 15 | string property1; 16 | string property2; 17 | 18 | [Required(ErrorMessage = "'Property1' message.")] public string Property1 { 19 | get => property1; set 20 | { 21 | property1 = value; 22 | OnPropertyChanged(); 23 | } 24 | } 25 | 26 | [Required(ErrorMessage = "'Property2' message.")] public string Property2 { 27 | get => property2; set 28 | { 29 | property2 = value; 30 | OnPropertyChanged(); 31 | } 32 | } 33 | 34 | public Model() 35 | { 36 | validationTemplate = new ValidationTemplate(this); 37 | } 38 | 39 | public string this[string columnName] => validationTemplate[columnName]; 40 | 41 | public string Error => validationTemplate.Error; 42 | 43 | public IEnumerable GetErrors(string propertyName) 44 | { 45 | return validationTemplate.GetErrors(propertyName); 46 | } 47 | 48 | bool INotifyDataErrorInfo.HasErrors => validationTemplate.HasErrors; 49 | 50 | event EventHandler INotifyDataErrorInfo.ErrorsChanged { 51 | add => validationTemplate.ErrorsChanged += value; remove => validationTemplate.ErrorsChanged -= value; } 52 | 53 | public event PropertyChangedEventHandler PropertyChanged; 54 | 55 | void OnPropertyChanged([CallerMemberName] string propertyName = null) 56 | { 57 | PropertyChanged?.Invoke(this, new PropertyChangedEventArgs(propertyName)); 58 | } 59 | } 60 | } -------------------------------------------------------------------------------- /Tests/Templates/DataAnnotations/ValidationTemplate.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | using System.Collections; 3 | using System.Collections.Generic; 4 | using System.ComponentModel; 5 | using System.ComponentModel.DataAnnotations; 6 | using System.Linq; 7 | 8 | namespace Templates.DataAnnotations 9 | { 10 | public class ValidationTemplate : 11 | IDataErrorInfo, 12 | INotifyDataErrorInfo 13 | { 14 | INotifyPropertyChanged target; 15 | ValidationContext validationContext; 16 | List validationResults; 17 | 18 | public ValidationTemplate(INotifyPropertyChanged target) 19 | { 20 | this.target = target; 21 | validationContext = new ValidationContext(target, null, null); 22 | validationResults = new List(); 23 | Validator.TryValidateObject(target, validationContext, validationResults, true); 24 | target.PropertyChanged += Validate; 25 | } 26 | 27 | void Validate(object sender, PropertyChangedEventArgs e) 28 | { 29 | validationResults.Clear(); 30 | Validator.TryValidateObject(target, validationContext, validationResults, true); 31 | var hashSet = new HashSet(validationResults.SelectMany(x => x.MemberNames)); 32 | foreach (var error in hashSet) 33 | { 34 | RaiseErrorsChanged(error); 35 | } 36 | } 37 | 38 | public IEnumerable GetErrors(string propertyName) 39 | { 40 | return validationResults.Where(x => x.MemberNames.Contains(propertyName)) 41 | .Select(x => x.ErrorMessage); 42 | } 43 | 44 | public bool HasErrors => validationResults.Count > 0; 45 | 46 | public string Error 47 | { 48 | get 49 | { 50 | var strings = validationResults.Select(x => x.ErrorMessage) 51 | .ToArray(); 52 | return string.Join(Environment.NewLine, strings); 53 | } 54 | } 55 | 56 | public string this[string propertyName] 57 | { 58 | get 59 | { 60 | var strings = validationResults.Where(x => x.MemberNames.Contains(propertyName)) 61 | .Select(x => x.ErrorMessage) 62 | .ToArray(); 63 | return string.Join(Environment.NewLine, strings); 64 | } 65 | } 66 | 67 | public event EventHandler ErrorsChanged; 68 | 69 | void RaiseErrorsChanged(string propertyName) 70 | { 71 | var handler = ErrorsChanged; 72 | handler?.Invoke(this, new DataErrorsChangedEventArgs(propertyName)); 73 | } 74 | } 75 | } -------------------------------------------------------------------------------- /Tests/TemplatesGeneric/DataAnnotations/DataAnnotationsTemplateTests.cs: -------------------------------------------------------------------------------- 1 | namespace TemplatesGeneric.DataAnnotations 2 | { 3 | using Xunit; 4 | 5 | public class TemplateTests 6 | { 7 | [Fact] 8 | public void DataErrorInfo() 9 | { 10 | ValidationTester.TestDataErrorInfo(new Model()); 11 | } 12 | 13 | [Fact] 14 | public void NotifyDataErrorInfo() 15 | { 16 | ValidationTester.TestNotifyDataErrorInfo(new Model()); 17 | } 18 | } 19 | } -------------------------------------------------------------------------------- /Tests/TemplatesGeneric/DataAnnotations/Model.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | using System.Collections; 3 | using System.ComponentModel; 4 | using System.ComponentModel.DataAnnotations; 5 | using System.Runtime.CompilerServices; 6 | 7 | namespace TemplatesGeneric.DataAnnotations 8 | { 9 | public class Model : 10 | IDataErrorInfo, 11 | INotifyPropertyChanged, 12 | INotifyDataErrorInfo 13 | { 14 | ValidationTemplate validationTemplate; 15 | string property1; 16 | string property2; 17 | [Required(ErrorMessage = "'Property1' message.")] public string Property1 { 18 | get => property1; set 19 | { 20 | property1 = value; 21 | OnPropertyChanged(); 22 | } 23 | } 24 | [Required(ErrorMessage = "'Property2' message.")] public string Property2 { 25 | get => property2; set 26 | { 27 | property2 = value; 28 | OnPropertyChanged(); 29 | } 30 | } 31 | 32 | public Model() 33 | { 34 | validationTemplate = new ValidationTemplate(this); 35 | } 36 | 37 | public event PropertyChangedEventHandler PropertyChanged; 38 | 39 | void OnPropertyChanged([CallerMemberName] string propertyName = null) 40 | { 41 | PropertyChanged?.Invoke(this, new PropertyChangedEventArgs(propertyName)); 42 | } 43 | public string this[string columnName] => validationTemplate[columnName]; 44 | 45 | public string Error => validationTemplate.Error; 46 | 47 | public IEnumerable GetErrors(string propertyName) 48 | { 49 | return validationTemplate.GetErrors(propertyName); 50 | } 51 | 52 | bool INotifyDataErrorInfo.HasErrors => validationTemplate.HasErrors; 53 | 54 | event EventHandler INotifyDataErrorInfo.ErrorsChanged { 55 | add => validationTemplate.ErrorsChanged += value; remove => validationTemplate.ErrorsChanged -= value; } 56 | } 57 | } -------------------------------------------------------------------------------- /Tests/TemplatesGeneric/DataAnnotations/ValidationTemplate.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | using System.Collections; 3 | using System.Collections.Generic; 4 | using System.ComponentModel; 5 | using System.ComponentModel.DataAnnotations; 6 | using System.Linq; 7 | 8 | namespace TemplatesGeneric.DataAnnotations 9 | { 10 | public class ValidationTemplate : 11 | IDataErrorInfo, 12 | INotifyDataErrorInfo 13 | where T: INotifyPropertyChanged 14 | { 15 | INotifyPropertyChanged target; 16 | ValidationContext validationContext; 17 | List validationResults; 18 | 19 | public ValidationTemplate(INotifyPropertyChanged target) 20 | { 21 | this.target = target; 22 | validationContext = new ValidationContext(target, null, null); 23 | validationResults = new List(); 24 | Validator.TryValidateObject(target, validationContext, validationResults, true); 25 | target.PropertyChanged += Validate; 26 | } 27 | 28 | void Validate(object sender, PropertyChangedEventArgs e) 29 | { 30 | validationResults.Clear(); 31 | Validator.TryValidateObject(target, validationContext, validationResults, true); 32 | var hashSet = new HashSet(validationResults.SelectMany(x => x.MemberNames)); 33 | foreach (var error in hashSet) 34 | { 35 | RaiseErrorsChanged(error); 36 | } 37 | } 38 | 39 | public IEnumerable GetErrors(string propertyName) 40 | { 41 | return validationResults.Where(x => x.MemberNames.Contains(propertyName)) 42 | .Select(x => x.ErrorMessage); 43 | } 44 | 45 | public bool HasErrors => validationResults.Count > 0; 46 | 47 | public string Error 48 | { 49 | get 50 | { 51 | var strings = validationResults.Select(x => x.ErrorMessage) 52 | .ToArray(); 53 | return string.Join(Environment.NewLine, strings); 54 | } 55 | } 56 | 57 | public string this[string propertyName] 58 | { 59 | get 60 | { 61 | var strings = validationResults.Where(x => x.MemberNames.Contains(propertyName)) 62 | .Select(x => x.ErrorMessage) 63 | .ToArray(); 64 | return string.Join(Environment.NewLine, strings); 65 | } 66 | } 67 | 68 | public event EventHandler ErrorsChanged; 69 | 70 | void RaiseErrorsChanged(string propertyName) 71 | { 72 | var handler = ErrorsChanged; 73 | handler?.Invoke(this, new DataErrorsChangedEventArgs(propertyName)); 74 | } 75 | } 76 | } -------------------------------------------------------------------------------- /Tests/TemplatesGeneric/FluentValidation/FluentTemplateTests.cs: -------------------------------------------------------------------------------- 1 | namespace TemplatesGeneric.FluentValidation 2 | { 3 | using Xunit; 4 | 5 | public class TemplateTests 6 | { 7 | [Fact] 8 | public void DataErrorInfo() 9 | { 10 | ValidationTester.TestDataErrorInfo(new Model()); 11 | } 12 | 13 | [Fact] 14 | public void NotifyDataErrorInfo() 15 | { 16 | ValidationTester.TestNotifyDataErrorInfo(new Model()); 17 | } 18 | } 19 | } -------------------------------------------------------------------------------- /Tests/TemplatesGeneric/FluentValidation/Model.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | using System.Collections; 3 | using System.ComponentModel; 4 | using System.Runtime.CompilerServices; 5 | 6 | namespace TemplatesGeneric.FluentValidation 7 | { 8 | public class Model : 9 | IDataErrorInfo, 10 | INotifyPropertyChanged, 11 | INotifyDataErrorInfo 12 | { 13 | ValidationTemplate validationTemplate; 14 | string property1; 15 | string property2; 16 | 17 | public string Property1 18 | { 19 | get => property1; 20 | set 21 | { 22 | property1 = value; 23 | OnPropertyChanged(); 24 | } 25 | } 26 | 27 | public string Property2 28 | { 29 | get => property2; 30 | set 31 | { 32 | property2 = value; 33 | OnPropertyChanged(); 34 | } 35 | } 36 | 37 | void OnPropertyChanged([CallerMemberName] string propertyName = null) 38 | { 39 | PropertyChanged?.Invoke(this, new PropertyChangedEventArgs(propertyName)); 40 | } 41 | 42 | public Model() 43 | { 44 | validationTemplate = new ValidationTemplate(this); 45 | } 46 | 47 | public event PropertyChangedEventHandler PropertyChanged; 48 | 49 | string IDataErrorInfo.this[string columnName] => validationTemplate[columnName]; 50 | 51 | public string Error => validationTemplate.Error; 52 | 53 | public IEnumerable GetErrors(string propertyName) 54 | { 55 | return validationTemplate.GetErrors(propertyName); 56 | } 57 | 58 | bool INotifyDataErrorInfo.HasErrors => validationTemplate.HasErrors; 59 | 60 | event EventHandler INotifyDataErrorInfo.ErrorsChanged 61 | { 62 | add => validationTemplate.ErrorsChanged += value; 63 | remove => validationTemplate.ErrorsChanged -= value; 64 | } 65 | } 66 | } -------------------------------------------------------------------------------- /Tests/TemplatesGeneric/FluentValidation/ModelValidator.cs: -------------------------------------------------------------------------------- 1 | using FluentValidation; 2 | 3 | namespace TemplatesGeneric.FluentValidation 4 | { 5 | public class ModelValidator : 6 | AbstractValidator 7 | { 8 | public ModelValidator() 9 | { 10 | RuleFor(x => x.Property1) 11 | .NotEmpty() 12 | .WithMessage("'Property1' message."); 13 | RuleFor(x => x.Property2) 14 | .NotEmpty() 15 | .WithMessage("'Property2' message."); 16 | } 17 | } 18 | } -------------------------------------------------------------------------------- /Tests/TemplatesGeneric/FluentValidation/ValidationFactory.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | using System.Collections.Concurrent; 3 | using System.ComponentModel; 4 | using FluentValidation; 5 | 6 | namespace TemplatesGeneric.FluentValidation 7 | { 8 | public static class ValidationFactory 9 | { 10 | static ConcurrentDictionary validators = new ConcurrentDictionary(); 11 | 12 | public static IValidator GetValidator() 13 | where T : INotifyPropertyChanged 14 | { var modelType = typeof (T); 15 | var modelTypeHandle = modelType.TypeHandle; 16 | if (!validators.TryGetValue(modelTypeHandle, out var validator)) 17 | { 18 | var typeName = $"{modelType.Namespace}.{modelType.Name}Validator"; 19 | var type = modelType.Assembly.GetType(typeName, true); 20 | validators[modelTypeHandle] = validator = (IValidator) Activator.CreateInstance(type); 21 | } 22 | 23 | return (IValidator) validator; 24 | } 25 | } 26 | } -------------------------------------------------------------------------------- /Tests/TemplatesGeneric/FluentValidation/ValidationTemplate.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | using System.Collections; 3 | using System.ComponentModel; 4 | using System.Linq; 5 | using FluentValidation; 6 | using FluentValidation.Results; 7 | 8 | namespace TemplatesGeneric.FluentValidation 9 | { 10 | public class ValidationTemplate : 11 | IDataErrorInfo, 12 | INotifyDataErrorInfo 13 | where T : INotifyPropertyChanged 14 | { 15 | IValidator validator; 16 | ValidationResult validationResult; 17 | ValidationContext context; 18 | 19 | public ValidationTemplate(T target) 20 | { 21 | validator = ValidationFactory.GetValidator(); 22 | context = new ValidationContext(target); 23 | validationResult = validator.Validate(context); 24 | target.PropertyChanged += Validate; 25 | } 26 | 27 | void Validate(object sender, PropertyChangedEventArgs e) 28 | { 29 | validationResult = validator.Validate(context); 30 | foreach (var error in validationResult.Errors) 31 | { 32 | RaiseErrorsChanged(error.PropertyName); 33 | } 34 | } 35 | 36 | public IEnumerable GetErrors(string propertyName) 37 | { 38 | return validationResult 39 | .Errors 40 | .Where(x => x.PropertyName == propertyName) 41 | .Select(x => x.ErrorMessage); 42 | } 43 | 44 | public bool HasErrors => validationResult.Errors.Count > 0; 45 | 46 | public string Error 47 | { 48 | get 49 | { 50 | var strings = validationResult.Errors 51 | .Select(x => x.ErrorMessage); 52 | return string.Join(Environment.NewLine, strings); 53 | } 54 | } 55 | 56 | public string this[string propertyName] 57 | { 58 | get 59 | { 60 | var strings = validationResult 61 | .Errors 62 | .Where(x => x.PropertyName == propertyName) 63 | .Select(x => x.ErrorMessage); 64 | return string.Join(Environment.NewLine, strings); 65 | } 66 | } 67 | 68 | public event EventHandler ErrorsChanged; 69 | 70 | void RaiseErrorsChanged(string propertyName) 71 | { 72 | var handler = ErrorsChanged; 73 | handler?.Invoke(this, new DataErrorsChangedEventArgs(propertyName)); 74 | } 75 | } 76 | } -------------------------------------------------------------------------------- /Tests/Tests.csproj: -------------------------------------------------------------------------------- 1 |  2 | 3 | 4 | net472;netcoreapp2.2 5 | true 6 | 7 | 8 | 9 | 10 | 11 | 12 | 13 | 14 | 15 | 16 | 17 | 18 | 19 | 20 | 21 | 22 | -------------------------------------------------------------------------------- /Tests/ValidationTester.cs: -------------------------------------------------------------------------------- 1 | using System.ComponentModel; 2 | using System.Linq; 3 | using Xunit; 4 | 5 | public static class ValidationTester 6 | { 7 | public static void TestDataErrorInfo(dynamic instance) 8 | { 9 | var dataErrorInfo = (IDataErrorInfo)instance; 10 | Assert.NotNull(dataErrorInfo.Error); 11 | Assert.NotEmpty(dataErrorInfo.Error); 12 | Assert.Equal("'Property1' message.", dataErrorInfo["Property1"]); 13 | Assert.Equal("'Property2' message.", dataErrorInfo["Property2"]); 14 | instance.Property1 = "foo"; 15 | instance.Property2 = "foo"; 16 | Assert.Empty(dataErrorInfo.Error); 17 | Assert.Empty(dataErrorInfo["GivenNames"]); 18 | Assert.Empty(dataErrorInfo["FamilyName"]); 19 | } 20 | 21 | public static void TestNotifyDataErrorInfo(dynamic instance) 22 | { 23 | var dataErrorInfo = (INotifyDataErrorInfo)instance; 24 | var errorsChangedCalled = false; 25 | dataErrorInfo.ErrorsChanged += (o, args) => { errorsChangedCalled = true; }; 26 | Assert.True(dataErrorInfo.HasErrors); 27 | Assert.Equal("'Property1' message.", dataErrorInfo.GetErrors("Property1").Cast().First()); 28 | Assert.Equal("'Property2' message.", dataErrorInfo.GetErrors("Property2").Cast().First()); 29 | instance.Property1 = "foo"; 30 | instance.Property2 = "foo"; 31 | Assert.False(dataErrorInfo.HasErrors); 32 | Assert.True(errorsChangedCalled); 33 | Assert.Empty(dataErrorInfo.GetErrors("GivenNames").Cast().ToList()); 34 | Assert.Empty(dataErrorInfo.GetErrors("FamilyName").Cast().ToList()); 35 | } 36 | } -------------------------------------------------------------------------------- /Validar.Fody/CecilExtensions.cs: -------------------------------------------------------------------------------- 1 | using System.Linq; 2 | using Fody; 3 | using Mono.Cecil; 4 | using Mono.Collections.Generic; 5 | 6 | public static class CecilExtensions 7 | { 8 | public static bool ContainsValidationAttribute(this Collection attributes) 9 | { 10 | var firstOrDefault = attributes.FirstOrDefault(x => x.Constructor.DeclaringType.Name == "InjectValidationAttribute"); 11 | if (firstOrDefault != null) 12 | { 13 | attributes.Remove(firstOrDefault); 14 | return true; 15 | } 16 | return false; 17 | } 18 | 19 | public static void ValidateIsOfType(this FieldReference targetReference, TypeReference expectedType) 20 | { 21 | if (targetReference.FieldType.FullName == expectedType.FullName) 22 | { 23 | return; 24 | } 25 | var message = $"Field '{targetReference.FullName}' could not be re-used because it is not the correct type. Expected '{expectedType.FullName}'."; 26 | throw new WeavingException(message); 27 | } 28 | } -------------------------------------------------------------------------------- /Validar.Fody/ImplementsInterface.cs: -------------------------------------------------------------------------------- 1 | using System.Linq; 2 | using Mono.Cecil; 3 | 4 | public static class ImplementsInterface 5 | { 6 | public static bool ImplementsINotify(this TypeReference typeReference) 7 | { 8 | if (typeReference is GenericParameter genericParameter) 9 | { 10 | return ImplementsINotify(genericParameter); 11 | } 12 | 13 | if (typeReference.Name == "Object") 14 | { 15 | return false; 16 | } 17 | 18 | if (typeReference.Name == "INotifyPropertyChanged") 19 | { 20 | return true; 21 | } 22 | 23 | var typeDefinition = typeReference.Resolve(); 24 | if (typeDefinition.Interfaces.Any(x => x.InterfaceType.Name == "INotifyPropertyChanged")) 25 | { 26 | return true; 27 | } 28 | 29 | if (typeDefinition.BaseType == null) 30 | { 31 | return false; 32 | } 33 | 34 | return typeDefinition.BaseType.ImplementsINotify(); 35 | } 36 | 37 | public static bool ImplementsINotify(this GenericParameter typeReference) 38 | { 39 | return typeReference.Constraints.Any(constraint => constraint.ConstraintType.ImplementsINotify()); 40 | } 41 | } -------------------------------------------------------------------------------- /Validar.Fody/Injectors/DataErrorInfoInjector.cs: -------------------------------------------------------------------------------- 1 | using System.Linq; 2 | using Mono.Cecil; 3 | using Mono.Cecil.Cil; 4 | using TypeSystem = Fody.TypeSystem; 5 | 6 | public class DataErrorInfoInjector 7 | { 8 | public TypeDefinition TypeDefinition; 9 | public TypeSystem TypeSystem; 10 | public DataErrorInfoFinder DataErrorInfoFinder; 11 | public ModuleWeaver ModuleWeaver; 12 | MethodAttributes MethodAttributes = MethodAttributes.Public | MethodAttributes.HideBySig | MethodAttributes.SpecialName | MethodAttributes.NewSlot | MethodAttributes.Virtual | MethodAttributes.Final; 13 | public FieldDefinition ValidationTemplateField; 14 | 15 | public void Execute() 16 | { 17 | if (TypeDefinition.Interfaces.Any(x => x.InterfaceType.Name == DataErrorInfoFinder.InterfaceRef.Name)) 18 | { 19 | var message = $"Skipped '{TypeDefinition.Name}' because it already implements '{DataErrorInfoFinder.InterfaceRef.Name}'."; 20 | ModuleWeaver.WriteInfo(message); 21 | return; 22 | } 23 | AddInterface(); 24 | AddGetError(); 25 | AddGetItem(); 26 | } 27 | 28 | void AddInterface() 29 | { 30 | TypeDefinition.Interfaces.Add(new InterfaceImplementation(DataErrorInfoFinder.InterfaceRef)); 31 | } 32 | 33 | void AddGetError() 34 | { 35 | var methodName = DataErrorInfoFinder.InterfaceRef.FullName + ".get_Error"; 36 | var method = new MethodDefinition(methodName, MethodAttributes, TypeSystem.StringReference) 37 | { 38 | IsGetter = true, 39 | IsPrivate = true 40 | }; 41 | method.Overrides.Add(DataErrorInfoFinder.GetErrorMethod); 42 | method.Body.Instructions.Append( 43 | Instruction.Create(OpCodes.Ldarg_0), 44 | Instruction.Create(OpCodes.Ldfld, ValidationTemplateField), 45 | Instruction.Create(OpCodes.Callvirt, DataErrorInfoFinder.GetErrorMethod), 46 | Instruction.Create(OpCodes.Ret) 47 | ); 48 | var propertyName = DataErrorInfoFinder.InterfaceRef.FullName + ".Error"; 49 | var property = new PropertyDefinition(propertyName, PropertyAttributes.None, TypeSystem.StringReference) 50 | { 51 | GetMethod = method 52 | }; 53 | TypeDefinition.Methods.Add(method); 54 | TypeDefinition.Properties.Add(property); 55 | } 56 | 57 | void AddGetItem() 58 | { 59 | var methodName = DataErrorInfoFinder.InterfaceRef.FullName + ".get_Item"; 60 | var method = new MethodDefinition(methodName, MethodAttributes, TypeSystem.StringReference) 61 | { 62 | IsGetter = true, 63 | IsPrivate = true, 64 | SemanticsAttributes = MethodSemanticsAttributes.Getter, 65 | }; 66 | method.Overrides.Add(DataErrorInfoFinder.GetItemMethod); 67 | method.Parameters.Add(new ParameterDefinition(TypeSystem.StringReference)); 68 | 69 | method.Body.Instructions.Append( 70 | Instruction.Create(OpCodes.Ldarg_0), 71 | Instruction.Create(OpCodes.Ldfld, ValidationTemplateField), 72 | Instruction.Create(OpCodes.Ldarg_1), 73 | Instruction.Create(OpCodes.Callvirt, DataErrorInfoFinder.GetItemMethod), 74 | Instruction.Create(OpCodes.Ret)); 75 | var propertyName = DataErrorInfoFinder.InterfaceRef.FullName + ".Item"; 76 | var property = new PropertyDefinition(propertyName, PropertyAttributes.None, TypeSystem.StringReference) 77 | { 78 | GetMethod = method, 79 | }; 80 | TypeDefinition.Methods.Add(method); 81 | 82 | TypeDefinition.Properties.Add(property); 83 | } 84 | } -------------------------------------------------------------------------------- /Validar.Fody/Injectors/NotifyDataErrorInfoInjector.cs: -------------------------------------------------------------------------------- 1 | using System.Linq; 2 | using Mono.Cecil; 3 | using Mono.Cecil.Cil; 4 | using TypeSystem = Fody.TypeSystem; 5 | 6 | public class NotifyDataErrorInfoInjector 7 | { 8 | public TypeDefinition TypeDefinition; 9 | public ModuleWeaver ModuleWeaver; 10 | public TypeSystem TypeSystem; 11 | public NotifyDataErrorInfoFinder NotifyDataErrorInfoFinder; 12 | MethodAttributes MethodAttributes = MethodAttributes.Public | MethodAttributes.HideBySig | MethodAttributes.SpecialName | MethodAttributes.NewSlot | MethodAttributes.Virtual | MethodAttributes.Final; 13 | public FieldDefinition ValidationTemplateField; 14 | 15 | public void Execute() 16 | { 17 | if (TypeDefinition.Interfaces.Any(x => x.InterfaceType.Name == NotifyDataErrorInfoFinder.InterfaceRef.Name)) 18 | { 19 | ModuleWeaver.WriteInfo($"Skipped '{TypeDefinition.Name}' because it already implements '{NotifyDataErrorInfoFinder.InterfaceRef.Name}'."); 20 | return; 21 | } 22 | AddInterface(); 23 | AddErrorsChanged(); 24 | AddHasErrors(); 25 | AddGetErrors(); 26 | } 27 | 28 | void AddGetErrors() 29 | { 30 | var name = NotifyDataErrorInfoFinder.InterfaceRef.FullName + ".GetErrors"; 31 | var method = new MethodDefinition(name, MethodAttributes, NotifyDataErrorInfoFinder.GetErrorsMethodRef.ReturnType) 32 | { 33 | IsPrivate = true, 34 | }; 35 | method.Overrides.Add(NotifyDataErrorInfoFinder.GetErrorsMethodRef); 36 | 37 | method.Parameters.Add(new ParameterDefinition(TypeSystem.StringReference)); 38 | 39 | method.Body.Instructions.Append( 40 | Instruction.Create(OpCodes.Ldarg_0), 41 | Instruction.Create(OpCodes.Ldfld, ValidationTemplateField), 42 | Instruction.Create(OpCodes.Ldarg_1), 43 | Instruction.Create(OpCodes.Callvirt, NotifyDataErrorInfoFinder.GetErrorsMethodRef), 44 | Instruction.Create(OpCodes.Ret) 45 | ); 46 | TypeDefinition.Methods.Add(method); 47 | } 48 | 49 | void AddHasErrors() 50 | { 51 | var methodName = NotifyDataErrorInfoFinder.InterfaceRef.FullName + ".get_HasErrors"; 52 | var method = new MethodDefinition(methodName, MethodAttributes, TypeSystem.BooleanReference) 53 | { 54 | IsGetter = true, 55 | IsPrivate = true, 56 | }; 57 | method.Overrides.Add(NotifyDataErrorInfoFinder.GetHasErrorsMethod); 58 | method.Body.Instructions.Append( 59 | Instruction.Create(OpCodes.Ldarg_0), 60 | Instruction.Create(OpCodes.Ldfld, ValidationTemplateField), 61 | Instruction.Create(OpCodes.Callvirt, NotifyDataErrorInfoFinder.GetHasErrorsMethod), 62 | Instruction.Create(OpCodes.Ret) 63 | ); 64 | var propertyName = NotifyDataErrorInfoFinder.InterfaceRef.FullName + ".HasErrors"; 65 | var property = new PropertyDefinition(propertyName, PropertyAttributes.None, TypeSystem.BooleanReference) 66 | { 67 | GetMethod = method, 68 | }; 69 | TypeDefinition.Methods.Add(method); 70 | TypeDefinition.Properties.Add(property); 71 | } 72 | 73 | void AddErrorsChanged() 74 | { 75 | var name = NotifyDataErrorInfoFinder.InterfaceRef.FullName + ".ErrorsChanged"; 76 | var eventDefinition = new EventDefinition(name, EventAttributes.SpecialName, NotifyDataErrorInfoFinder.ErrorsChangedEventType) 77 | { 78 | AddMethod = GetAdd(), 79 | RemoveMethod = GetRemove(), 80 | }; 81 | 82 | TypeDefinition.Events.Add(eventDefinition); 83 | } 84 | 85 | MethodDefinition GetAdd() 86 | { 87 | var name = NotifyDataErrorInfoFinder.InterfaceRef.FullName + ".add_ErrorsChanged"; 88 | var add = new MethodDefinition(name, MethodAttributes, TypeSystem.VoidReference) 89 | { 90 | SemanticsAttributes = MethodSemanticsAttributes.AddOn, 91 | IsPrivate = true, 92 | }; 93 | add.Overrides.Add(NotifyDataErrorInfoFinder.ErrorsChangedAddMethod); 94 | add.Parameters.Add(new ParameterDefinition(NotifyDataErrorInfoFinder.ErrorsChangedEventType)); 95 | add.Body.Instructions.Append( 96 | Instruction.Create(OpCodes.Ldarg_0), 97 | Instruction.Create(OpCodes.Ldfld, ValidationTemplateField), 98 | Instruction.Create(OpCodes.Ldarg_1), 99 | Instruction.Create(OpCodes.Callvirt, NotifyDataErrorInfoFinder.ErrorsChangedAddMethod), 100 | Instruction.Create(OpCodes.Ret)); 101 | TypeDefinition.Methods.Add(add); 102 | return add; 103 | } 104 | 105 | MethodDefinition GetRemove() 106 | { 107 | var name = NotifyDataErrorInfoFinder.InterfaceRef.FullName + ".remove_ErrorsChanged"; 108 | var remove = new MethodDefinition(name, MethodAttributes, TypeSystem.VoidReference) 109 | { 110 | SemanticsAttributes = MethodSemanticsAttributes.RemoveOn, 111 | IsPrivate = true 112 | }; 113 | remove.Overrides.Add(NotifyDataErrorInfoFinder.ErrorsChangedRemoveMethod); 114 | remove.Parameters.Add(new ParameterDefinition(NotifyDataErrorInfoFinder.ErrorsChangedEventType)); 115 | remove.Body.Instructions.Append( 116 | Instruction.Create(OpCodes.Ldarg_0), 117 | Instruction.Create(OpCodes.Ldfld, ValidationTemplateField), 118 | Instruction.Create(OpCodes.Ldarg_1), 119 | Instruction.Create(OpCodes.Callvirt, NotifyDataErrorInfoFinder.ErrorsChangedRemoveMethod), 120 | Instruction.Create(OpCodes.Ret)); 121 | TypeDefinition.Methods.Add(remove); 122 | return remove; 123 | } 124 | 125 | void AddInterface() 126 | { 127 | TypeDefinition.Interfaces.Add(new InterfaceImplementation(NotifyDataErrorInfoFinder.InterfaceRef)); 128 | } 129 | } -------------------------------------------------------------------------------- /Validar.Fody/InstructionListExtensions.cs: -------------------------------------------------------------------------------- 1 | using Mono.Cecil.Cil; 2 | using Mono.Collections.Generic; 3 | 4 | public static class InstructionListExtensions 5 | { 6 | public static void BeforeLast(this Collection collection, params Instruction[] instructions) 7 | { 8 | var index = collection.Count - 1; 9 | foreach (var instruction in instructions) 10 | { 11 | collection.Insert(index, instruction); 12 | index++; 13 | } 14 | } 15 | 16 | public static void Append(this Collection collection, params Instruction[] instructions) 17 | { 18 | for (var index = 0; index < instructions.Length; index++) 19 | { 20 | collection.Insert(index, instructions[index]); 21 | } 22 | } 23 | } -------------------------------------------------------------------------------- /Validar.Fody/ModuleWeaver.cs: -------------------------------------------------------------------------------- 1 | using System.Collections.Generic; 2 | using System.Linq; 3 | using Fody; 4 | using Mono.Cecil; 5 | 6 | public class ModuleWeaver: BaseModuleWeaver 7 | { 8 | NotifyDataErrorInfoFinder notifyDataErrorInfoFinder; 9 | DataErrorInfoFinder dataErrorInfoFinder; 10 | ValidationTemplateFinder templateFinder; 11 | 12 | public override void Execute() 13 | { 14 | templateFinder = new ValidationTemplateFinder 15 | { 16 | LogInfo = x => base.WriteInfo(x), 17 | ModuleDefinition = ModuleDefinition, 18 | }; 19 | templateFinder.Execute(); 20 | 21 | dataErrorInfoFinder = new DataErrorInfoFinder 22 | { 23 | ValidationTemplateFinder = templateFinder, 24 | ModuleDefinition = ModuleDefinition, 25 | }; 26 | dataErrorInfoFinder.Execute(); 27 | 28 | notifyDataErrorInfoFinder = new NotifyDataErrorInfoFinder 29 | { 30 | ValidationTemplateFinder = templateFinder, 31 | ModuleDefinition = ModuleDefinition, 32 | }; 33 | notifyDataErrorInfoFinder.Execute(); 34 | 35 | 36 | if (!dataErrorInfoFinder.Found && !notifyDataErrorInfoFinder.Found) 37 | { 38 | throw new WeavingException("Found ValidationTemplate but it did not implement INotifyDataErrorInfo or IDataErrorInfo"); 39 | } 40 | 41 | ProcessTypes(); 42 | } 43 | 44 | public override IEnumerable GetAssembliesForScanning() 45 | { 46 | return Enumerable.Empty(); 47 | } 48 | 49 | public override bool ShouldCleanReference => true; 50 | 51 | public void ProcessTypes() 52 | { 53 | foreach (var type in ModuleDefinition.GetTypes().Where(x => x.IsClass)) 54 | { 55 | ProcessType(type); 56 | } 57 | } 58 | 59 | public void ProcessType(TypeDefinition type) 60 | { 61 | var containsValidationAttribute = type.CustomAttributes.ContainsValidationAttribute(); 62 | if (!type.ImplementsINotify()) 63 | { 64 | if (containsValidationAttribute) 65 | { 66 | throw new WeavingException($"Found [InjectValidationAttribute] on '{type.Name}' but it doesn't implement INotifyPropertyChanged so cannot inject."); 67 | } 68 | 69 | return; 70 | } 71 | 72 | if (!containsValidationAttribute) 73 | { 74 | return; 75 | } 76 | 77 | if (type.HasGenericParameters) 78 | { 79 | throw new WeavingException($"Failed to process '{type.FullName}'. Generic models are not supported. Feel free to send a pull request."); 80 | } 81 | 82 | var templateFieldInjector = new TemplateFieldInjector 83 | { 84 | ValidationTemplateFinder = templateFinder, 85 | TargetType = type, 86 | ModuleDefinition = ModuleDefinition, 87 | TypeSystem = TypeSystem 88 | }; 89 | templateFieldInjector.AddField(); 90 | 91 | if (dataErrorInfoFinder.Found) 92 | { 93 | var injector = new DataErrorInfoInjector 94 | { 95 | TypeDefinition = type, 96 | TypeSystem = TypeSystem, 97 | DataErrorInfoFinder = dataErrorInfoFinder, 98 | ModuleWeaver = this, 99 | ValidationTemplateField = templateFieldInjector.ValidationTemplateField 100 | }; 101 | injector.Execute(); 102 | } 103 | 104 | if (notifyDataErrorInfoFinder.Found) 105 | { 106 | var injector = new NotifyDataErrorInfoInjector 107 | { 108 | TypeDefinition = type, 109 | NotifyDataErrorInfoFinder = notifyDataErrorInfoFinder, 110 | TypeSystem = TypeSystem, 111 | ModuleWeaver = this, 112 | ValidationTemplateField = templateFieldInjector.ValidationTemplateField 113 | }; 114 | injector.Execute(); 115 | } 116 | } 117 | } -------------------------------------------------------------------------------- /Validar.Fody/ReturnFixer.cs: -------------------------------------------------------------------------------- 1 | using System.Linq; 2 | using Mono.Cecil.Cil; 3 | 4 | public static class ReturnFixer 5 | { 6 | public static void MakeLastStatementReturn(this MethodBody method) 7 | { 8 | var instructions = method.Instructions; 9 | var last = instructions.Last(); 10 | 11 | var count = method.Instructions.Count; 12 | count--; 13 | var secondLastInstruction = method.Instructions[count]; 14 | 15 | if (secondLastInstruction.OpCode != OpCodes.Nop) 16 | { 17 | secondLastInstruction = Instruction.Create(OpCodes.Nop); 18 | instructions.BeforeLast(secondLastInstruction); 19 | } 20 | else 21 | { 22 | count--; 23 | } 24 | 25 | 26 | foreach (var exceptionHandler in method.ExceptionHandlers) 27 | { 28 | if (exceptionHandler.HandlerEnd == last) 29 | { 30 | exceptionHandler.HandlerEnd = secondLastInstruction; 31 | } 32 | } 33 | for (var index = 0; index < count; index++) 34 | { 35 | var instruction = instructions[index]; 36 | if (instruction.OpCode == OpCodes.Ret) 37 | { 38 | instructions[index] = Instruction.Create(OpCodes.Br, secondLastInstruction); 39 | } 40 | if (instruction.Operand == last) 41 | { 42 | instructions[index] = Instruction.Create(instruction.OpCode, secondLastInstruction); 43 | } 44 | } 45 | } 46 | } -------------------------------------------------------------------------------- /Validar.Fody/TemplateFieldInjector.cs: -------------------------------------------------------------------------------- 1 | using System.Linq; 2 | using Mono.Cecil; 3 | using Mono.Cecil.Cil; 4 | using Mono.Cecil.Rocks; 5 | 6 | public class TemplateFieldInjector 7 | { 8 | public ValidationTemplateFinder ValidationTemplateFinder; 9 | public FieldDefinition ValidationTemplateField; 10 | public TypeDefinition TargetType; 11 | public ModuleDefinition ModuleDefinition; 12 | public Fody.TypeSystem TypeSystem; 13 | 14 | public void AddField() 15 | { 16 | ValidationTemplateField = TargetType.Fields.FirstOrDefault(x => x.Name == "validationTemplate"); 17 | var fieldType = ValidationTemplateFinder.TypeReference; 18 | if (fieldType.HasGenericParameters) 19 | { 20 | var genericInstanceType = new GenericInstanceType(fieldType); 21 | genericInstanceType.GenericArguments.Add(TargetType); 22 | fieldType = genericInstanceType; 23 | } 24 | if (ValidationTemplateField != null) 25 | { 26 | ValidationTemplateField.ValidateIsOfType(fieldType); 27 | return; 28 | } 29 | ValidationTemplateField = new FieldDefinition("validationTemplate", FieldAttributes.Private, fieldType); 30 | TargetType.Fields.Add(ValidationTemplateField); 31 | AddConstructor(); 32 | } 33 | 34 | void AddConstructor() 35 | { 36 | foreach (var constructor in TargetType.GetConstructors().Where(c => !c.IsStatic)) 37 | { 38 | ProcessConstructor(constructor); 39 | } 40 | } 41 | 42 | public void ProcessConstructor(MethodDefinition targetConstructor) 43 | { 44 | MethodReference templateConstructor; 45 | if (ValidationTemplateFinder.TypeReference.HasGenericParameters) 46 | { 47 | var makeGenericInstanceType = ValidationTemplateFinder.TypeDefinition.MakeGenericInstanceType(TargetType); 48 | var reference = new MethodReference(".ctor", TypeSystem.VoidReference, makeGenericInstanceType) 49 | { 50 | HasThis = true, 51 | }; 52 | 53 | foreach (var parameter in ValidationTemplateFinder.TemplateConstructor.Parameters) 54 | { 55 | reference.Parameters.Add(new ParameterDefinition(parameter.ParameterType)); 56 | } 57 | 58 | templateConstructor = ModuleDefinition.ImportReference(reference); 59 | } 60 | else 61 | { 62 | templateConstructor = ValidationTemplateFinder.TemplateConstructor; 63 | } 64 | 65 | var body = targetConstructor.Body; 66 | body.SimplifyMacros(); 67 | body.MakeLastStatementReturn(); 68 | body.Instructions.BeforeLast( 69 | Instruction.Create(OpCodes.Ldarg_0), 70 | Instruction.Create(OpCodes.Ldarg_0), 71 | Instruction.Create(OpCodes.Newobj, templateConstructor), 72 | Instruction.Create(OpCodes.Stfld, ValidationTemplateField) 73 | ); 74 | body.OptimizeMacros(); 75 | } 76 | } -------------------------------------------------------------------------------- /Validar.Fody/TemplateFinders/DataErrorInfoFinder.cs: -------------------------------------------------------------------------------- 1 | using System.Linq; 2 | using Mono.Cecil; 3 | 4 | public class DataErrorInfoFinder 5 | { 6 | public ValidationTemplateFinder ValidationTemplateFinder; 7 | public bool Found = true; 8 | public MethodReference GetErrorMethod; 9 | public ModuleDefinition ModuleDefinition; 10 | public MethodReference GetItemMethod; 11 | public TypeReference InterfaceRef; 12 | 13 | public void Execute() 14 | { 15 | var interfaces = ValidationTemplateFinder.TypeDefinition.Interfaces; 16 | InterfaceRef = interfaces.Select(x=>x.InterfaceType) 17 | .FirstOrDefault(x => x.Name == "IDataErrorInfo"); 18 | if (InterfaceRef == null) 19 | { 20 | Found = false; 21 | return; 22 | } 23 | var interfaceType = InterfaceRef.Resolve(); 24 | InterfaceRef = ModuleDefinition.ImportReference(InterfaceRef); 25 | 26 | GetErrorMethod = ModuleDefinition.ImportReference(interfaceType.Methods.First(x => x.Name == "get_Error")); 27 | GetItemMethod = ModuleDefinition.ImportReference(interfaceType.Methods.First(x => x.Name == "get_Item")); 28 | } 29 | } -------------------------------------------------------------------------------- /Validar.Fody/TemplateFinders/NotifyDataErrorInfoFinder.cs: -------------------------------------------------------------------------------- 1 | using System.Linq; 2 | using Mono.Cecil; 3 | 4 | public class NotifyDataErrorInfoFinder 5 | { 6 | public TypeReference InterfaceRef; 7 | public bool Found = true; 8 | public MethodReference ErrorsChangedAddMethod; 9 | public MethodReference ErrorsChangedRemoveMethod; 10 | public EventDefinition ErrorsChangedEvent; 11 | public MethodReference GetErrorsMethodRef; 12 | public ValidationTemplateFinder ValidationTemplateFinder; 13 | public TypeReference ErrorsChangedEventType; 14 | public ModuleDefinition ModuleDefinition; 15 | public MethodDefinition GetErrorsMethodDef; 16 | public MethodReference GetHasErrorsMethod; 17 | 18 | public void Execute() 19 | { 20 | var interfaces = ValidationTemplateFinder.TypeDefinition.Interfaces; 21 | InterfaceRef = interfaces.Select(x => x.InterfaceType) 22 | .FirstOrDefault(x => x.Name == "INotifyDataErrorInfo"); 23 | if (InterfaceRef == null) 24 | { 25 | Found = false; 26 | return; 27 | } 28 | var interfaceType = InterfaceRef.Resolve(); 29 | InterfaceRef = ModuleDefinition.ImportReference(InterfaceRef); 30 | 31 | GetErrorsMethodDef = interfaceType.Methods.First(x => x.Name == "GetErrors"); 32 | GetErrorsMethodRef = ModuleDefinition.ImportReference(GetErrorsMethodDef); 33 | ErrorsChangedEvent = interfaceType.Events.First(x => x.Name == "ErrorsChanged"); 34 | ErrorsChangedEventType = ModuleDefinition.ImportReference(ErrorsChangedEvent.EventType); 35 | ErrorsChangedAddMethod = ModuleDefinition.ImportReference(ErrorsChangedEvent.AddMethod); 36 | ErrorsChangedRemoveMethod = ModuleDefinition.ImportReference(ErrorsChangedEvent.RemoveMethod); 37 | 38 | GetHasErrorsMethod = ModuleDefinition.ImportReference(interfaceType.Methods.First(x => x.Name == "get_HasErrors")); 39 | } 40 | } -------------------------------------------------------------------------------- /Validar.Fody/Validar.Fody.csproj: -------------------------------------------------------------------------------- 1 |  2 | 3 | 4 | netstandard2.0 5 | true 6 | 7 | 8 | 9 | 10 | -------------------------------------------------------------------------------- /Validar.Fody/ValidationTemplateFinder.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | using System.Linq; 3 | using Fody; 4 | using Mono.Cecil; 5 | 6 | public class ValidationTemplateFinder 7 | { 8 | public TypeReference TypeReference; 9 | public TypeDefinition TypeDefinition; 10 | public MethodReference TemplateConstructor; 11 | public Action LogInfo; 12 | public ModuleDefinition ModuleDefinition; 13 | 14 | public void Execute() 15 | { 16 | var validationTemplateAttribute = ModuleDefinition 17 | .Assembly 18 | .CustomAttributes 19 | .FirstOrDefault(x=>x.AttributeType.Name == "ValidationTemplateAttribute"); 20 | 21 | if (validationTemplateAttribute == null) 22 | { 23 | ImportInternal(); 24 | return; 25 | } 26 | ImportExternal(validationTemplateAttribute); 27 | } 28 | 29 | void ImportExternal(CustomAttribute validationTemplateAttribute) 30 | { 31 | var typeReference = (TypeReference) validationTemplateAttribute.ConstructorArguments.First().Value; 32 | TypeDefinition = typeReference.Resolve(); 33 | TypeReference = ModuleDefinition.ImportReference(TypeDefinition); 34 | 35 | TemplateConstructor = ModuleDefinition.ImportReference(FindConstructor(TypeDefinition)); 36 | ModuleDefinition 37 | .Assembly 38 | .CustomAttributes.Remove(validationTemplateAttribute); 39 | } 40 | 41 | void ImportInternal() 42 | { 43 | LogInfo("Could not find a 'ValidationTemplateAttribute' on the current assembly. Going to search current assembly for 'ValidationTemplate'."); 44 | 45 | TypeDefinition = ModuleDefinition 46 | .GetTypes() 47 | .FirstOrDefault(x => 48 | x.Name == "ValidationTemplate" || 49 | x.Name == "ValidationTemplate`1"); 50 | if (TypeDefinition == null) 51 | { 52 | throw new WeavingException("Could not find a type named 'ValidationTemplate'"); 53 | } 54 | TypeReference = TypeDefinition; 55 | 56 | TemplateConstructor = FindConstructor(TypeDefinition); 57 | } 58 | 59 | MethodDefinition FindConstructor(TypeDefinition typeDefinition) 60 | { 61 | var templateConstructor = typeDefinition.Methods.FirstOrDefault(x => x.IsConstructor && x.Parameters.Count == 1 && x.Parameters.First().ParameterType.ImplementsINotify()); 62 | if (templateConstructor == null) 63 | { 64 | throw new WeavingException("Found 'ValidationTemplate' but it did not have a constructor that takes 'INotifyPropertyChanged' as a parameter"); 65 | } 66 | 67 | return templateConstructor; 68 | } 69 | } -------------------------------------------------------------------------------- /Validar.sln: -------------------------------------------------------------------------------- 1 |  2 | Microsoft Visual Studio Solution File, Format Version 12.00 3 | # Visual Studio Version 16 4 | VisualStudioVersion = 16.0.29201.188 5 | MinimumVisualStudioVersion = 16.0.29201.188 6 | Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "Validar.Fody", "Validar.Fody\Validar.Fody.csproj", "{C3578A7B-09A6-4444-9383-0DEAFA4958BD}" 7 | EndProject 8 | Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "WithGenericExternal", "WithGenericExternal\WithGenericExternal.csproj", "{68655477-ACC8-4A10-8C59-DC7644AE5303}" 9 | EndProject 10 | Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "WithGenericInternal", "WithGenericInternal\WithGenericInternal.csproj", "{3256D062-E246-4B58-89CE-3809C6398C99}" 11 | EndProject 12 | Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "ExternalValidation", "ExternalValidation\ExternalValidation.csproj", "{BEB0322F-1AB0-4E09-BBCB-3FDA5FE7655A}" 13 | EndProject 14 | Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "Tests", "Tests\Tests.csproj", "{D9CE7424-74B3-42B2-A3A8-84447FC2D362}" 15 | EndProject 16 | Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "Validar", "Validar\Validar.csproj", "{B5AEB0E8-28F4-4955-A055-9C200F7113F0}" 17 | ProjectSection(ProjectDependencies) = postProject 18 | {C3578A7B-09A6-4444-9383-0DEAFA4958BD} = {C3578A7B-09A6-4444-9383-0DEAFA4958BD} 19 | EndProjectSection 20 | EndProject 21 | Project("{2150E333-8FDC-42A3-9474-1A3956D46DE8}") = "Solution Items", "Solution Items", "{5853776D-89F0-489F-BD58-D5B88D9F8A11}" 22 | ProjectSection(SolutionItems) = preProject 23 | Directory.Build.props = Directory.Build.props 24 | EndProjectSection 25 | EndProject 26 | Global 27 | GlobalSection(SolutionConfigurationPlatforms) = preSolution 28 | Debug|Any CPU = Debug|Any CPU 29 | Debug|Mixed Platforms = Debug|Mixed Platforms 30 | Debug|x86 = Debug|x86 31 | Release|Any CPU = Release|Any CPU 32 | Release|Mixed Platforms = Release|Mixed Platforms 33 | Release|x86 = Release|x86 34 | EndGlobalSection 35 | GlobalSection(ProjectConfigurationPlatforms) = postSolution 36 | {C3578A7B-09A6-4444-9383-0DEAFA4958BD}.Debug|Any CPU.ActiveCfg = Debug|Any CPU 37 | {C3578A7B-09A6-4444-9383-0DEAFA4958BD}.Debug|Any CPU.Build.0 = Debug|Any CPU 38 | {C3578A7B-09A6-4444-9383-0DEAFA4958BD}.Debug|Mixed Platforms.ActiveCfg = Debug|Any CPU 39 | {C3578A7B-09A6-4444-9383-0DEAFA4958BD}.Debug|Mixed Platforms.Build.0 = Debug|Any CPU 40 | {C3578A7B-09A6-4444-9383-0DEAFA4958BD}.Debug|x86.ActiveCfg = Debug|Any CPU 41 | {C3578A7B-09A6-4444-9383-0DEAFA4958BD}.Release|Any CPU.ActiveCfg = Release|Any CPU 42 | {C3578A7B-09A6-4444-9383-0DEAFA4958BD}.Release|Any CPU.Build.0 = Release|Any CPU 43 | {C3578A7B-09A6-4444-9383-0DEAFA4958BD}.Release|Mixed Platforms.ActiveCfg = Release|Any CPU 44 | {C3578A7B-09A6-4444-9383-0DEAFA4958BD}.Release|Mixed Platforms.Build.0 = Release|Any CPU 45 | {C3578A7B-09A6-4444-9383-0DEAFA4958BD}.Release|x86.ActiveCfg = Release|Any CPU 46 | {68655477-ACC8-4A10-8C59-DC7644AE5303}.Debug|Any CPU.ActiveCfg = Debug|Any CPU 47 | {68655477-ACC8-4A10-8C59-DC7644AE5303}.Debug|Any CPU.Build.0 = Debug|Any CPU 48 | {68655477-ACC8-4A10-8C59-DC7644AE5303}.Debug|Mixed Platforms.ActiveCfg = Debug|Any CPU 49 | {68655477-ACC8-4A10-8C59-DC7644AE5303}.Debug|Mixed Platforms.Build.0 = Debug|Any CPU 50 | {68655477-ACC8-4A10-8C59-DC7644AE5303}.Debug|x86.ActiveCfg = Debug|Any CPU 51 | {68655477-ACC8-4A10-8C59-DC7644AE5303}.Release|Any CPU.ActiveCfg = Release|Any CPU 52 | {68655477-ACC8-4A10-8C59-DC7644AE5303}.Release|Any CPU.Build.0 = Release|Any CPU 53 | {68655477-ACC8-4A10-8C59-DC7644AE5303}.Release|Mixed Platforms.ActiveCfg = Release|Any CPU 54 | {68655477-ACC8-4A10-8C59-DC7644AE5303}.Release|Mixed Platforms.Build.0 = Release|Any CPU 55 | {68655477-ACC8-4A10-8C59-DC7644AE5303}.Release|x86.ActiveCfg = Release|Any CPU 56 | {3256D062-E246-4B58-89CE-3809C6398C99}.Debug|Any CPU.ActiveCfg = Debug|Any CPU 57 | {3256D062-E246-4B58-89CE-3809C6398C99}.Debug|Any CPU.Build.0 = Debug|Any CPU 58 | {3256D062-E246-4B58-89CE-3809C6398C99}.Debug|Mixed Platforms.ActiveCfg = Debug|Any CPU 59 | {3256D062-E246-4B58-89CE-3809C6398C99}.Debug|Mixed Platforms.Build.0 = Debug|Any CPU 60 | {3256D062-E246-4B58-89CE-3809C6398C99}.Debug|x86.ActiveCfg = Debug|Any CPU 61 | {3256D062-E246-4B58-89CE-3809C6398C99}.Release|Any CPU.ActiveCfg = Release|Any CPU 62 | {3256D062-E246-4B58-89CE-3809C6398C99}.Release|Any CPU.Build.0 = Release|Any CPU 63 | {3256D062-E246-4B58-89CE-3809C6398C99}.Release|Mixed Platforms.ActiveCfg = Release|Any CPU 64 | {3256D062-E246-4B58-89CE-3809C6398C99}.Release|Mixed Platforms.Build.0 = Release|Any CPU 65 | {3256D062-E246-4B58-89CE-3809C6398C99}.Release|x86.ActiveCfg = Release|Any CPU 66 | {BEB0322F-1AB0-4E09-BBCB-3FDA5FE7655A}.Debug|Any CPU.ActiveCfg = Debug|Any CPU 67 | {BEB0322F-1AB0-4E09-BBCB-3FDA5FE7655A}.Debug|Any CPU.Build.0 = Debug|Any CPU 68 | {BEB0322F-1AB0-4E09-BBCB-3FDA5FE7655A}.Debug|Mixed Platforms.ActiveCfg = Debug|Any CPU 69 | {BEB0322F-1AB0-4E09-BBCB-3FDA5FE7655A}.Debug|Mixed Platforms.Build.0 = Debug|Any CPU 70 | {BEB0322F-1AB0-4E09-BBCB-3FDA5FE7655A}.Debug|x86.ActiveCfg = Debug|Any CPU 71 | {BEB0322F-1AB0-4E09-BBCB-3FDA5FE7655A}.Release|Any CPU.ActiveCfg = Release|Any CPU 72 | {BEB0322F-1AB0-4E09-BBCB-3FDA5FE7655A}.Release|Any CPU.Build.0 = Release|Any CPU 73 | {BEB0322F-1AB0-4E09-BBCB-3FDA5FE7655A}.Release|Mixed Platforms.ActiveCfg = Release|Any CPU 74 | {BEB0322F-1AB0-4E09-BBCB-3FDA5FE7655A}.Release|Mixed Platforms.Build.0 = Release|Any CPU 75 | {BEB0322F-1AB0-4E09-BBCB-3FDA5FE7655A}.Release|x86.ActiveCfg = Release|Any CPU 76 | {D9CE7424-74B3-42B2-A3A8-84447FC2D362}.Debug|Any CPU.ActiveCfg = Debug|Any CPU 77 | {D9CE7424-74B3-42B2-A3A8-84447FC2D362}.Debug|Any CPU.Build.0 = Debug|Any CPU 78 | {D9CE7424-74B3-42B2-A3A8-84447FC2D362}.Debug|Mixed Platforms.ActiveCfg = Debug|Any CPU 79 | {D9CE7424-74B3-42B2-A3A8-84447FC2D362}.Debug|Mixed Platforms.Build.0 = Debug|Any CPU 80 | {D9CE7424-74B3-42B2-A3A8-84447FC2D362}.Debug|x86.ActiveCfg = Debug|Any CPU 81 | {D9CE7424-74B3-42B2-A3A8-84447FC2D362}.Debug|x86.Build.0 = Debug|Any CPU 82 | {D9CE7424-74B3-42B2-A3A8-84447FC2D362}.Release|Any CPU.ActiveCfg = Release|Any CPU 83 | {D9CE7424-74B3-42B2-A3A8-84447FC2D362}.Release|Any CPU.Build.0 = Release|Any CPU 84 | {D9CE7424-74B3-42B2-A3A8-84447FC2D362}.Release|Mixed Platforms.ActiveCfg = Release|Any CPU 85 | {D9CE7424-74B3-42B2-A3A8-84447FC2D362}.Release|Mixed Platforms.Build.0 = Release|Any CPU 86 | {D9CE7424-74B3-42B2-A3A8-84447FC2D362}.Release|x86.ActiveCfg = Release|Any CPU 87 | {D9CE7424-74B3-42B2-A3A8-84447FC2D362}.Release|x86.Build.0 = Release|Any CPU 88 | {B5AEB0E8-28F4-4955-A055-9C200F7113F0}.Debug|Any CPU.ActiveCfg = Debug|Any CPU 89 | {B5AEB0E8-28F4-4955-A055-9C200F7113F0}.Debug|Any CPU.Build.0 = Debug|Any CPU 90 | {B5AEB0E8-28F4-4955-A055-9C200F7113F0}.Debug|Mixed Platforms.ActiveCfg = Debug|Any CPU 91 | {B5AEB0E8-28F4-4955-A055-9C200F7113F0}.Debug|Mixed Platforms.Build.0 = Debug|Any CPU 92 | {B5AEB0E8-28F4-4955-A055-9C200F7113F0}.Debug|x86.ActiveCfg = Debug|Any CPU 93 | {B5AEB0E8-28F4-4955-A055-9C200F7113F0}.Release|Any CPU.ActiveCfg = Release|Any CPU 94 | {B5AEB0E8-28F4-4955-A055-9C200F7113F0}.Release|Any CPU.Build.0 = Release|Any CPU 95 | {B5AEB0E8-28F4-4955-A055-9C200F7113F0}.Release|Mixed Platforms.ActiveCfg = Release|Any CPU 96 | {B5AEB0E8-28F4-4955-A055-9C200F7113F0}.Release|Mixed Platforms.Build.0 = Release|Any CPU 97 | {B5AEB0E8-28F4-4955-A055-9C200F7113F0}.Release|x86.ActiveCfg = Release|Any CPU 98 | EndGlobalSection 99 | GlobalSection(SolutionProperties) = preSolution 100 | HideSolutionNode = FALSE 101 | EndGlobalSection 102 | GlobalSection(ExtensibilityGlobals) = postSolution 103 | SolutionGuid = {A8938CB6-E5FC-4233-BCC4-DA6E72E9C0AA} 104 | EndGlobalSection 105 | EndGlobal 106 | -------------------------------------------------------------------------------- /Validar/InjectValidationAttribute.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | 3 | namespace Validar 4 | { 5 | /// 6 | /// Used to flag items as requiring validation. 7 | /// 8 | [AttributeUsage(AttributeTargets.Class)] 9 | public class InjectValidationAttribute : Attribute 10 | { 11 | } 12 | } -------------------------------------------------------------------------------- /Validar/Validar.csproj: -------------------------------------------------------------------------------- 1 |  2 | 3 | 4 | net452;netstandard1.4;netstandard2.0;netstandard2.1 5 | true 6 | true 7 | key.snk 8 | Simon Cropp 9 | Fody add-in for validation in XAML bindings. 10 | Validation, ILWeaving, Fody, Cecil, IDataErrorInfo, INotifyDataErrorInfo, WPF, XAML 11 | $(SolutionDir)nugets 12 | https://raw.githubusercontent.com/Fody/Validar/master/package_icon.png 13 | https://github.com/Fody/Validar 14 | 15 | 16 | 17 | 18 | 19 | -------------------------------------------------------------------------------- /Validar/ValidationTemplateAttribute.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | 3 | namespace Validar 4 | { 5 | /// 6 | /// Used to point to the correct validation Template. 7 | /// 8 | [AttributeUsage(AttributeTargets.Assembly)] 9 | public class ValidationTemplateAttribute : Attribute 10 | { 11 | /// 12 | /// Construct a new instance of 13 | /// 14 | /// The validation template to use. 15 | // ReSharper disable once UnusedParameter.Local 16 | public ValidationTemplateAttribute(Type validationTemplate) 17 | { 18 | } 19 | } 20 | } -------------------------------------------------------------------------------- /Validar/key.snk: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/fodyarchived/Validar/1fc56edd9fb3d238873693a79852b0b296d29044/Validar/key.snk -------------------------------------------------------------------------------- /WithGenericExternal/Annotations.cs: -------------------------------------------------------------------------------- 1 | /* MIT License 2 | 3 | Copyright (c) 2016 JetBrains http://www.jetbrains.com 4 | 5 | Permission is hereby granted, free of charge, to any person obtaining a copy 6 | of this software and associated documentation files (the "Software"), to deal 7 | in the Software without restriction, including without limitation the rights 8 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 9 | copies of the Software, and to permit persons to whom the Software is 10 | furnished to do so, subject to the following conditions: 11 | 12 | The above copyright notice and this permission notice shall be included in all 13 | copies or substantial portions of the Software. 14 | 15 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 16 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 17 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 18 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 19 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 20 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE 21 | SOFTWARE. */ 22 | 23 | using System; 24 | 25 | #pragma warning disable 1591 26 | // ReSharper disable UnusedMember.Global 27 | // ReSharper disable MemberCanBePrivate.Global 28 | // ReSharper disable UnusedAutoPropertyAccessor.Global 29 | // ReSharper disable IntroduceOptionalParameters.Global 30 | // ReSharper disable MemberCanBeProtected.Global 31 | // ReSharper disable InconsistentNaming 32 | 33 | namespace WithGenericExternal.Annotations 34 | { 35 | /// 36 | /// Indicates that the value of the marked element could be null sometimes, 37 | /// so the check for null is necessary before its usage. 38 | /// 39 | /// 40 | /// [CanBeNull] object Test() => null; 41 | /// 42 | /// void UseTest() { 43 | /// var p = Test(); 44 | /// var s = p.ToString(); // Warning: Possible 'System.NullReferenceException' 45 | /// } 46 | /// 47 | [AttributeUsage( 48 | AttributeTargets.Method | AttributeTargets.Parameter | AttributeTargets.Property | 49 | AttributeTargets.Delegate | AttributeTargets.Field | AttributeTargets.Event | 50 | AttributeTargets.Class | AttributeTargets.Interface | AttributeTargets.GenericParameter)] 51 | public sealed class CanBeNullAttribute : Attribute { } 52 | 53 | /// 54 | /// Indicates that the value of the marked element could never be null. 55 | /// 56 | /// 57 | /// [NotNull] object Foo() { 58 | /// return null; // Warning: Possible 'null' assignment 59 | /// } 60 | /// 61 | [AttributeUsage( 62 | AttributeTargets.Method | AttributeTargets.Parameter | AttributeTargets.Property | 63 | AttributeTargets.Delegate | AttributeTargets.Field | AttributeTargets.Event | 64 | AttributeTargets.Class | AttributeTargets.Interface | AttributeTargets.GenericParameter)] 65 | public sealed class NotNullAttribute : Attribute { } 66 | 67 | /// 68 | /// Can be appplied to symbols of types derived from IEnumerable as well as to symbols of Task 69 | /// and Lazy classes to indicate that the value of a collection item, of the Task.Result property 70 | /// or of the Lazy.Value property can never be null. 71 | /// 72 | [AttributeUsage( 73 | AttributeTargets.Method | AttributeTargets.Parameter | AttributeTargets.Property | 74 | AttributeTargets.Delegate | AttributeTargets.Field)] 75 | public sealed class ItemNotNullAttribute : Attribute { } 76 | 77 | /// 78 | /// Can be appplied to symbols of types derived from IEnumerable as well as to symbols of Task 79 | /// and Lazy classes to indicate that the value of a collection item, of the Task.Result property 80 | /// or of the Lazy.Value property can be null. 81 | /// 82 | [AttributeUsage( 83 | AttributeTargets.Method | AttributeTargets.Parameter | AttributeTargets.Property | 84 | AttributeTargets.Delegate | AttributeTargets.Field)] 85 | public sealed class ItemCanBeNullAttribute : Attribute { } 86 | 87 | /// 88 | /// Indicates that the marked method builds string by format pattern and (optional) arguments. 89 | /// Parameter, which contains format string, should be given in constructor. The format string 90 | /// should be in -like form. 91 | /// 92 | /// 93 | /// [StringFormatMethod("message")] 94 | /// void ShowError(string message, params object[] args) { /* do something */ } 95 | /// 96 | /// void Foo() { 97 | /// ShowError("Failed: {0}"); // Warning: Non-existing argument in format string 98 | /// } 99 | /// 100 | [AttributeUsage( 101 | AttributeTargets.Constructor | AttributeTargets.Method | 102 | AttributeTargets.Property | AttributeTargets.Delegate)] 103 | public sealed class StringFormatMethodAttribute : Attribute 104 | { 105 | /// 106 | /// Specifies which parameter of an annotated method should be treated as format-string 107 | /// 108 | public StringFormatMethodAttribute([NotNull] string formatParameterName) 109 | { 110 | FormatParameterName = formatParameterName; 111 | } 112 | 113 | [NotNull] public string FormatParameterName { get; private set; } 114 | } 115 | 116 | /// 117 | /// For a parameter that is expected to be one of the limited set of values. 118 | /// Specify fields of which type should be used as values for this parameter. 119 | /// 120 | [AttributeUsage( 121 | AttributeTargets.Parameter | AttributeTargets.Property | AttributeTargets.Field, 122 | AllowMultiple = true)] 123 | public sealed class ValueProviderAttribute : Attribute 124 | { 125 | public ValueProviderAttribute([NotNull] string name) 126 | { 127 | Name = name; 128 | } 129 | 130 | [NotNull] public string Name { get; private set; } 131 | } 132 | 133 | /// 134 | /// Indicates that the function argument should be string literal and match one 135 | /// of the parameters of the caller function. For example, ReSharper annotates 136 | /// the parameter of . 137 | /// 138 | /// 139 | /// void Foo(string param) { 140 | /// if (param == null) 141 | /// throw new ArgumentNullException("par"); // Warning: Cannot resolve symbol 142 | /// } 143 | /// 144 | [AttributeUsage(AttributeTargets.Parameter)] 145 | public sealed class InvokerParameterNameAttribute : Attribute { } 146 | 147 | /// 148 | /// Indicates that the method is contained in a type that implements 149 | /// System.ComponentModel.INotifyPropertyChanged interface and this method 150 | /// is used to notify that some property value changed. 151 | /// 152 | /// 153 | /// The method should be non-static and conform to one of the supported signatures: 154 | /// 155 | /// NotifyChanged(string) 156 | /// NotifyChanged(params string[]) 157 | /// NotifyChanged{T}(Expression{Func{T}}) 158 | /// NotifyChanged{T,U}(Expression{Func{T,U}}) 159 | /// SetProperty{T}(ref T, T, string) 160 | /// 161 | /// 162 | /// 163 | /// public class Foo : INotifyPropertyChanged { 164 | /// public event PropertyChangedEventHandler PropertyChanged; 165 | /// 166 | /// [NotifyPropertyChangedInvocator] 167 | /// protected virtual void NotifyChanged(string propertyName) { ... } 168 | /// 169 | /// string _name; 170 | /// 171 | /// public string Name { 172 | /// get { return _name; } 173 | /// set { _name = value; NotifyChanged("LastName"); /* Warning */ } 174 | /// } 175 | /// } 176 | /// 177 | /// Examples of generated notifications: 178 | /// 179 | /// NotifyChanged("Property") 180 | /// NotifyChanged(() => Property) 181 | /// NotifyChanged((VM x) => x.Property) 182 | /// SetProperty(ref myField, value, "Property") 183 | /// 184 | /// 185 | [AttributeUsage(AttributeTargets.Method)] 186 | public sealed class NotifyPropertyChangedInvocatorAttribute : Attribute 187 | { 188 | public NotifyPropertyChangedInvocatorAttribute() { } 189 | public NotifyPropertyChangedInvocatorAttribute([NotNull] string parameterName) 190 | { 191 | ParameterName = parameterName; 192 | } 193 | 194 | [CanBeNull] public string ParameterName { get; private set; } 195 | } 196 | 197 | /// 198 | /// Describes dependency between method input and output. 199 | /// 200 | /// 201 | ///

Function Definition Table syntax:

202 | /// 203 | /// FDT ::= FDTRow [;FDTRow]* 204 | /// FDTRow ::= Input => Output | Output <= Input 205 | /// Input ::= ParameterName: Value [, Input]* 206 | /// Output ::= [ParameterName: Value]* {halt|stop|void|nothing|Value} 207 | /// Value ::= true | false | null | notnull | canbenull 208 | /// 209 | /// If method has single input parameter, it's name could be omitted.
210 | /// Using halt (or void/nothing, which is the same) for method output 211 | /// means that the methos doesn't return normally (throws or terminates the process).
212 | /// Value canbenull is only applicable for output parameters.
213 | /// You can use multiple [ContractAnnotation] for each FDT row, or use single attribute 214 | /// with rows separated by semicolon. There is no notion of order rows, all rows are checked 215 | /// for applicability and applied per each program state tracked by R# analysis.
216 | ///
217 | /// 218 | /// 219 | /// [ContractAnnotation("=> halt")] 220 | /// public void TerminationMethod() 221 | /// 222 | /// 223 | /// [ContractAnnotation("halt <= condition: false")] 224 | /// public void Assert(bool condition, string text) // regular assertion method 225 | /// 226 | /// 227 | /// [ContractAnnotation("s:null => true")] 228 | /// public bool IsNullOrEmpty(string s) // string.IsNullOrEmpty() 229 | /// 230 | /// 231 | /// // A method that returns null if the parameter is null, 232 | /// // and not null if the parameter is not null 233 | /// [ContractAnnotation("null => null; notnull => notnull")] 234 | /// public object Transform(object data) 235 | /// 236 | /// 237 | /// [ContractAnnotation("=> true, result: notnull; => false, result: null")] 238 | /// public bool TryParse(string s, out Person result) 239 | /// 240 | /// 241 | [AttributeUsage(AttributeTargets.Method, AllowMultiple = true)] 242 | public sealed class ContractAnnotationAttribute : Attribute 243 | { 244 | public ContractAnnotationAttribute([NotNull] string contract) 245 | : this(contract, false) { } 246 | 247 | public ContractAnnotationAttribute([NotNull] string contract, bool forceFullStates) 248 | { 249 | Contract = contract; 250 | ForceFullStates = forceFullStates; 251 | } 252 | 253 | [NotNull] public string Contract { get; private set; } 254 | 255 | public bool ForceFullStates { get; private set; } 256 | } 257 | 258 | /// 259 | /// Indicates that marked element should be localized or not. 260 | /// 261 | /// 262 | /// [LocalizationRequiredAttribute(true)] 263 | /// class Foo { 264 | /// string str = "my string"; // Warning: Localizable string 265 | /// } 266 | /// 267 | [AttributeUsage(AttributeTargets.All)] 268 | public sealed class LocalizationRequiredAttribute : Attribute 269 | { 270 | public LocalizationRequiredAttribute() : this(true) { } 271 | 272 | public LocalizationRequiredAttribute(bool required) 273 | { 274 | Required = required; 275 | } 276 | 277 | public bool Required { get; private set; } 278 | } 279 | 280 | /// 281 | /// Indicates that the value of the marked type (or its derivatives) 282 | /// cannot be compared using '==' or '!=' operators and Equals() 283 | /// should be used instead. However, using '==' or '!=' for comparison 284 | /// with null is always permitted. 285 | /// 286 | /// 287 | /// [CannotApplyEqualityOperator] 288 | /// class NoEquality { } 289 | /// 290 | /// class UsesNoEquality { 291 | /// void Test() { 292 | /// var ca1 = new NoEquality(); 293 | /// var ca2 = new NoEquality(); 294 | /// if (ca1 != null) { // OK 295 | /// bool condition = ca1 == ca2; // Warning 296 | /// } 297 | /// } 298 | /// } 299 | /// 300 | [AttributeUsage(AttributeTargets.Interface | AttributeTargets.Class | AttributeTargets.Struct)] 301 | public sealed class CannotApplyEqualityOperatorAttribute : Attribute { } 302 | 303 | /// 304 | /// When applied to a target attribute, specifies a requirement for any type marked 305 | /// with the target attribute to implement or inherit specific type or types. 306 | /// 307 | /// 308 | /// [BaseTypeRequired(typeof(IComponent)] // Specify requirement 309 | /// class ComponentAttribute : Attribute { } 310 | /// 311 | /// [Component] // ComponentAttribute requires implementing IComponent interface 312 | /// class MyComponent : IComponent { } 313 | /// 314 | [AttributeUsage(AttributeTargets.Class, AllowMultiple = true)] 315 | [BaseTypeRequired(typeof(Attribute))] 316 | public sealed class BaseTypeRequiredAttribute : Attribute 317 | { 318 | public BaseTypeRequiredAttribute([NotNull] Type baseType) 319 | { 320 | BaseType = baseType; 321 | } 322 | 323 | [NotNull] public Type BaseType { get; private set; } 324 | } 325 | 326 | /// 327 | /// Indicates that the marked symbol is used implicitly (e.g. via reflection, in external library), 328 | /// so this symbol will not be marked as unused (as well as by other usage inspections). 329 | /// 330 | [AttributeUsage(AttributeTargets.All)] 331 | public sealed class UsedImplicitlyAttribute : Attribute 332 | { 333 | public UsedImplicitlyAttribute() 334 | : this(ImplicitUseKindFlags.Default, ImplicitUseTargetFlags.Default) { } 335 | 336 | public UsedImplicitlyAttribute(ImplicitUseKindFlags useKindFlags) 337 | : this(useKindFlags, ImplicitUseTargetFlags.Default) { } 338 | 339 | public UsedImplicitlyAttribute(ImplicitUseTargetFlags targetFlags) 340 | : this(ImplicitUseKindFlags.Default, targetFlags) { } 341 | 342 | public UsedImplicitlyAttribute(ImplicitUseKindFlags useKindFlags, ImplicitUseTargetFlags targetFlags) 343 | { 344 | UseKindFlags = useKindFlags; 345 | TargetFlags = targetFlags; 346 | } 347 | 348 | public ImplicitUseKindFlags UseKindFlags { get; private set; } 349 | 350 | public ImplicitUseTargetFlags TargetFlags { get; private set; } 351 | } 352 | 353 | /// 354 | /// Should be used on attributes and causes ReSharper to not mark symbols marked with such attributes 355 | /// as unused (as well as by other usage inspections) 356 | /// 357 | [AttributeUsage(AttributeTargets.Class | AttributeTargets.GenericParameter)] 358 | public sealed class MeansImplicitUseAttribute : Attribute 359 | { 360 | public MeansImplicitUseAttribute() 361 | : this(ImplicitUseKindFlags.Default, ImplicitUseTargetFlags.Default) { } 362 | 363 | public MeansImplicitUseAttribute(ImplicitUseKindFlags useKindFlags) 364 | : this(useKindFlags, ImplicitUseTargetFlags.Default) { } 365 | 366 | public MeansImplicitUseAttribute(ImplicitUseTargetFlags targetFlags) 367 | : this(ImplicitUseKindFlags.Default, targetFlags) { } 368 | 369 | public MeansImplicitUseAttribute(ImplicitUseKindFlags useKindFlags, ImplicitUseTargetFlags targetFlags) 370 | { 371 | UseKindFlags = useKindFlags; 372 | TargetFlags = targetFlags; 373 | } 374 | 375 | [UsedImplicitly] public ImplicitUseKindFlags UseKindFlags { get; private set; } 376 | 377 | [UsedImplicitly] public ImplicitUseTargetFlags TargetFlags { get; private set; } 378 | } 379 | 380 | [Flags] 381 | public enum ImplicitUseKindFlags 382 | { 383 | Default = Access | Assign | InstantiatedWithFixedConstructorSignature, 384 | /// Only entity marked with attribute considered used. 385 | Access = 1, 386 | /// Indicates implicit assignment to a member. 387 | Assign = 2, 388 | /// 389 | /// Indicates implicit instantiation of a type with fixed constructor signature. 390 | /// That means any unused constructor parameters won't be reported as such. 391 | /// 392 | InstantiatedWithFixedConstructorSignature = 4, 393 | /// Indicates implicit instantiation of a type. 394 | InstantiatedNoFixedConstructorSignature = 8, 395 | } 396 | 397 | /// 398 | /// Specify what is considered used implicitly when marked 399 | /// with or . 400 | /// 401 | [Flags] 402 | public enum ImplicitUseTargetFlags 403 | { 404 | Default = Itself, 405 | Itself = 1, 406 | /// Members of entity marked with attribute are considered used. 407 | Members = 2, 408 | /// Entity marked with attribute and all its members considered used. 409 | WithMembers = Itself | Members 410 | } 411 | 412 | /// 413 | /// This attribute is intended to mark publicly available API 414 | /// which should not be removed and so is treated as used. 415 | /// 416 | [MeansImplicitUse(ImplicitUseTargetFlags.WithMembers)] 417 | public sealed class PublicAPIAttribute : Attribute 418 | { 419 | public PublicAPIAttribute() { } 420 | 421 | public PublicAPIAttribute([NotNull] string comment) 422 | { 423 | Comment = comment; 424 | } 425 | 426 | [CanBeNull] public string Comment { get; private set; } 427 | } 428 | 429 | /// 430 | /// Tells code analysis engine if the parameter is completely handled when the invoked method is on stack. 431 | /// If the parameter is a delegate, indicates that delegate is executed while the method is executed. 432 | /// If the parameter is an enumerable, indicates that it is enumerated while the method is executed. 433 | /// 434 | [AttributeUsage(AttributeTargets.Parameter)] 435 | public sealed class InstantHandleAttribute : Attribute { } 436 | 437 | /// 438 | /// Indicates that a method does not make any observable state changes. 439 | /// The same as System.Diagnostics.Contracts.PureAttribute. 440 | /// 441 | /// 442 | /// [Pure] int Multiply(int x, int y) => x * y; 443 | /// 444 | /// void M() { 445 | /// Multiply(123, 42); // Waring: Return value of pure method is not used 446 | /// } 447 | /// 448 | [AttributeUsage(AttributeTargets.Method)] 449 | public sealed class PureAttribute : Attribute { } 450 | 451 | /// 452 | /// Indicates that the return value of method invocation must be used. 453 | /// 454 | [AttributeUsage(AttributeTargets.Method)] 455 | public sealed class MustUseReturnValueAttribute : Attribute 456 | { 457 | public MustUseReturnValueAttribute() { } 458 | 459 | public MustUseReturnValueAttribute([NotNull] string justification) 460 | { 461 | Justification = justification; 462 | } 463 | 464 | [CanBeNull] public string Justification { get; private set; } 465 | } 466 | 467 | /// 468 | /// Indicates the type member or parameter of some type, that should be used instead of all other ways 469 | /// to get the value that type. This annotation is useful when you have some "context" value evaluated 470 | /// and stored somewhere, meaning that all other ways to get this value must be consolidated with existing one. 471 | /// 472 | /// 473 | /// class Foo { 474 | /// [ProvidesContext] IBarService _barService = ...; 475 | /// 476 | /// void ProcessNode(INode node) { 477 | /// DoSomething(node, node.GetGlobalServices().Bar); 478 | /// // ^ Warning: use value of '_barService' field 479 | /// } 480 | /// } 481 | /// 482 | [AttributeUsage( 483 | AttributeTargets.Field | AttributeTargets.Property | AttributeTargets.Parameter | AttributeTargets.Method | 484 | AttributeTargets.Class | AttributeTargets.Interface | AttributeTargets.Struct | AttributeTargets.GenericParameter)] 485 | public sealed class ProvidesContextAttribute : Attribute { } 486 | 487 | /// 488 | /// Indicates that a parameter is a path to a file or a folder within a web project. 489 | /// Path can be relative or absolute, starting from web root (~). 490 | /// 491 | [AttributeUsage(AttributeTargets.Parameter)] 492 | public sealed class PathReferenceAttribute : Attribute 493 | { 494 | public PathReferenceAttribute() { } 495 | 496 | public PathReferenceAttribute([NotNull, PathReference] string basePath) 497 | { 498 | BasePath = basePath; 499 | } 500 | 501 | [CanBeNull] public string BasePath { get; private set; } 502 | } 503 | 504 | /// 505 | /// An extension method marked with this attribute is processed by ReSharper code completion 506 | /// as a 'Source Template'. When extension method is completed over some expression, it's source code 507 | /// is automatically expanded like a template at call site. 508 | /// 509 | /// 510 | /// Template method body can contain valid source code and/or special comments starting with '$'. 511 | /// Text inside these comments is added as source code when the template is applied. Template parameters 512 | /// can be used either as additional method parameters or as identifiers wrapped in two '$' signs. 513 | /// Use the attribute to specify macros for parameters. 514 | /// 515 | /// 516 | /// In this example, the 'forEach' method is a source template available over all values 517 | /// of enumerable types, producing ordinary C# 'foreach' statement and placing caret inside block: 518 | /// 519 | /// [SourceTemplate] 520 | /// public static void forEach<T>(this IEnumerable<T> xs) { 521 | /// foreach (var x in xs) { 522 | /// //$ $END$ 523 | /// } 524 | /// } 525 | /// 526 | /// 527 | [AttributeUsage(AttributeTargets.Method)] 528 | public sealed class SourceTemplateAttribute : Attribute { } 529 | 530 | /// 531 | /// Allows specifying a macro for a parameter of a source template. 532 | /// 533 | /// 534 | /// You can apply the attribute on the whole method or on any of its additional parameters. The macro expression 535 | /// is defined in the property. When applied on a method, the target 536 | /// template parameter is defined in the property. To apply the macro silently 537 | /// for the parameter, set the property value = -1. 538 | /// 539 | /// 540 | /// Applying the attribute on a source template method: 541 | /// 542 | /// [SourceTemplate, Macro(Target = "item", Expression = "suggestVariableName()")] 543 | /// public static void forEach<T>(this IEnumerable<T> collection) { 544 | /// foreach (var item in collection) { 545 | /// //$ $END$ 546 | /// } 547 | /// } 548 | /// 549 | /// Applying the attribute on a template method parameter: 550 | /// 551 | /// [SourceTemplate] 552 | /// public static void something(this Entity x, [Macro(Expression = "guid()", Editable = -1)] string newguid) { 553 | /// /*$ var $x$Id = "$newguid$" + x.ToString(); 554 | /// x.DoSomething($x$Id); */ 555 | /// } 556 | /// 557 | /// 558 | [AttributeUsage(AttributeTargets.Parameter | AttributeTargets.Method, AllowMultiple = true)] 559 | public sealed class MacroAttribute : Attribute 560 | { 561 | /// 562 | /// Allows specifying a macro that will be executed for a source template 563 | /// parameter when the template is expanded. 564 | /// 565 | [CanBeNull] public string Expression { get; set; } 566 | 567 | /// 568 | /// Allows specifying which occurrence of the target parameter becomes editable when the template is deployed. 569 | /// 570 | /// 571 | /// If the target parameter is used several times in the template, only one occurrence becomes editable; 572 | /// other occurrences are changed synchronously. To specify the zero-based index of the editable occurrence, 573 | /// use values >= 0. To make the parameter non-editable when the template is expanded, use -1. 574 | /// > 575 | public int Editable { get; set; } 576 | 577 | /// 578 | /// Identifies the target parameter of a source template if the 579 | /// is applied on a template method. 580 | /// 581 | [CanBeNull] public string Target { get; set; } 582 | } 583 | 584 | [AttributeUsage(AttributeTargets.Assembly | AttributeTargets.Field | AttributeTargets.Property, AllowMultiple = true)] 585 | public sealed class AspMvcAreaMasterLocationFormatAttribute : Attribute 586 | { 587 | public AspMvcAreaMasterLocationFormatAttribute([NotNull] string format) 588 | { 589 | Format = format; 590 | } 591 | 592 | [NotNull] public string Format { get; private set; } 593 | } 594 | 595 | [AttributeUsage(AttributeTargets.Assembly | AttributeTargets.Field | AttributeTargets.Property, AllowMultiple = true)] 596 | public sealed class AspMvcAreaPartialViewLocationFormatAttribute : Attribute 597 | { 598 | public AspMvcAreaPartialViewLocationFormatAttribute([NotNull] string format) 599 | { 600 | Format = format; 601 | } 602 | 603 | [NotNull] public string Format { get; private set; } 604 | } 605 | 606 | [AttributeUsage(AttributeTargets.Assembly | AttributeTargets.Field | AttributeTargets.Property, AllowMultiple = true)] 607 | public sealed class AspMvcAreaViewLocationFormatAttribute : Attribute 608 | { 609 | public AspMvcAreaViewLocationFormatAttribute([NotNull] string format) 610 | { 611 | Format = format; 612 | } 613 | 614 | [NotNull] public string Format { get; private set; } 615 | } 616 | 617 | [AttributeUsage(AttributeTargets.Assembly | AttributeTargets.Field | AttributeTargets.Property, AllowMultiple = true)] 618 | public sealed class AspMvcMasterLocationFormatAttribute : Attribute 619 | { 620 | public AspMvcMasterLocationFormatAttribute([NotNull] string format) 621 | { 622 | Format = format; 623 | } 624 | 625 | [NotNull] public string Format { get; private set; } 626 | } 627 | 628 | [AttributeUsage(AttributeTargets.Assembly | AttributeTargets.Field | AttributeTargets.Property, AllowMultiple = true)] 629 | public sealed class AspMvcPartialViewLocationFormatAttribute : Attribute 630 | { 631 | public AspMvcPartialViewLocationFormatAttribute([NotNull] string format) 632 | { 633 | Format = format; 634 | } 635 | 636 | [NotNull] public string Format { get; private set; } 637 | } 638 | 639 | [AttributeUsage(AttributeTargets.Assembly | AttributeTargets.Field | AttributeTargets.Property, AllowMultiple = true)] 640 | public sealed class AspMvcViewLocationFormatAttribute : Attribute 641 | { 642 | public AspMvcViewLocationFormatAttribute([NotNull] string format) 643 | { 644 | Format = format; 645 | } 646 | 647 | [NotNull] public string Format { get; private set; } 648 | } 649 | 650 | /// 651 | /// ASP.NET MVC attribute. If applied to a parameter, indicates that the parameter 652 | /// is an MVC action. If applied to a method, the MVC action name is calculated 653 | /// implicitly from the context. Use this attribute for custom wrappers similar to 654 | /// System.Web.Mvc.Html.ChildActionExtensions.RenderAction(HtmlHelper, String). 655 | /// 656 | [AttributeUsage(AttributeTargets.Parameter | AttributeTargets.Method)] 657 | public sealed class AspMvcActionAttribute : Attribute 658 | { 659 | public AspMvcActionAttribute() { } 660 | 661 | public AspMvcActionAttribute([NotNull] string anonymousProperty) 662 | { 663 | AnonymousProperty = anonymousProperty; 664 | } 665 | 666 | [CanBeNull] public string AnonymousProperty { get; private set; } 667 | } 668 | 669 | /// 670 | /// ASP.NET MVC attribute. Indicates that a parameter is an MVC area. 671 | /// Use this attribute for custom wrappers similar to 672 | /// System.Web.Mvc.Html.ChildActionExtensions.RenderAction(HtmlHelper, String). 673 | /// 674 | [AttributeUsage(AttributeTargets.Parameter)] 675 | public sealed class AspMvcAreaAttribute : Attribute 676 | { 677 | public AspMvcAreaAttribute() { } 678 | 679 | public AspMvcAreaAttribute([NotNull] string anonymousProperty) 680 | { 681 | AnonymousProperty = anonymousProperty; 682 | } 683 | 684 | [CanBeNull] public string AnonymousProperty { get; private set; } 685 | } 686 | 687 | /// 688 | /// ASP.NET MVC attribute. If applied to a parameter, indicates that the parameter is 689 | /// an MVC controller. If applied to a method, the MVC controller name is calculated 690 | /// implicitly from the context. Use this attribute for custom wrappers similar to 691 | /// System.Web.Mvc.Html.ChildActionExtensions.RenderAction(HtmlHelper, String, String). 692 | /// 693 | [AttributeUsage(AttributeTargets.Parameter | AttributeTargets.Method)] 694 | public sealed class AspMvcControllerAttribute : Attribute 695 | { 696 | public AspMvcControllerAttribute() { } 697 | 698 | public AspMvcControllerAttribute([NotNull] string anonymousProperty) 699 | { 700 | AnonymousProperty = anonymousProperty; 701 | } 702 | 703 | [CanBeNull] public string AnonymousProperty { get; private set; } 704 | } 705 | 706 | /// 707 | /// ASP.NET MVC attribute. Indicates that a parameter is an MVC Master. Use this attribute 708 | /// for custom wrappers similar to System.Web.Mvc.Controller.View(String, String). 709 | /// 710 | [AttributeUsage(AttributeTargets.Parameter)] 711 | public sealed class AspMvcMasterAttribute : Attribute { } 712 | 713 | /// 714 | /// ASP.NET MVC attribute. Indicates that a parameter is an MVC model type. Use this attribute 715 | /// for custom wrappers similar to System.Web.Mvc.Controller.View(String, Object). 716 | /// 717 | [AttributeUsage(AttributeTargets.Parameter)] 718 | public sealed class AspMvcModelTypeAttribute : Attribute { } 719 | 720 | /// 721 | /// ASP.NET MVC attribute. If applied to a parameter, indicates that the parameter is an MVC 722 | /// partial view. If applied to a method, the MVC partial view name is calculated implicitly 723 | /// from the context. Use this attribute for custom wrappers similar to 724 | /// System.Web.Mvc.Html.RenderPartialExtensions.RenderPartial(HtmlHelper, String). 725 | /// 726 | [AttributeUsage(AttributeTargets.Parameter | AttributeTargets.Method)] 727 | public sealed class AspMvcPartialViewAttribute : Attribute { } 728 | 729 | /// 730 | /// ASP.NET MVC attribute. Allows disabling inspections for MVC views within a class or a method. 731 | /// 732 | [AttributeUsage(AttributeTargets.Class | AttributeTargets.Method)] 733 | public sealed class AspMvcSuppressViewErrorAttribute : Attribute { } 734 | 735 | /// 736 | /// ASP.NET MVC attribute. Indicates that a parameter is an MVC display template. 737 | /// Use this attribute for custom wrappers similar to 738 | /// System.Web.Mvc.Html.DisplayExtensions.DisplayForModel(HtmlHelper, String). 739 | /// 740 | [AttributeUsage(AttributeTargets.Parameter)] 741 | public sealed class AspMvcDisplayTemplateAttribute : Attribute { } 742 | 743 | /// 744 | /// ASP.NET MVC attribute. Indicates that a parameter is an MVC editor template. 745 | /// Use this attribute for custom wrappers similar to 746 | /// System.Web.Mvc.Html.EditorExtensions.EditorForModel(HtmlHelper, String). 747 | /// 748 | [AttributeUsage(AttributeTargets.Parameter)] 749 | public sealed class AspMvcEditorTemplateAttribute : Attribute { } 750 | 751 | /// 752 | /// ASP.NET MVC attribute. Indicates that a parameter is an MVC template. 753 | /// Use this attribute for custom wrappers similar to 754 | /// System.ComponentModel.DataAnnotations.UIHintAttribute(System.String). 755 | /// 756 | [AttributeUsage(AttributeTargets.Parameter)] 757 | public sealed class AspMvcTemplateAttribute : Attribute { } 758 | 759 | /// 760 | /// ASP.NET MVC attribute. If applied to a parameter, indicates that the parameter 761 | /// is an MVC view component. If applied to a method, the MVC view name is calculated implicitly 762 | /// from the context. Use this attribute for custom wrappers similar to 763 | /// System.Web.Mvc.Controller.View(Object). 764 | /// 765 | [AttributeUsage(AttributeTargets.Parameter | AttributeTargets.Method)] 766 | public sealed class AspMvcViewAttribute : Attribute { } 767 | 768 | /// 769 | /// ASP.NET MVC attribute. If applied to a parameter, indicates that the parameter 770 | /// is an MVC view component name. 771 | /// 772 | [AttributeUsage(AttributeTargets.Parameter)] 773 | public sealed class AspMvcViewComponentAttribute : Attribute { } 774 | 775 | /// 776 | /// ASP.NET MVC attribute. If applied to a parameter, indicates that the parameter 777 | /// is an MVC view component view. If applied to a method, the MVC view component view name is default. 778 | /// 779 | [AttributeUsage(AttributeTargets.Parameter | AttributeTargets.Method)] 780 | public sealed class AspMvcViewComponentViewAttribute : Attribute { } 781 | 782 | /// 783 | /// ASP.NET MVC attribute. When applied to a parameter of an attribute, 784 | /// indicates that this parameter is an MVC action name. 785 | /// 786 | /// 787 | /// [ActionName("Foo")] 788 | /// public ActionResult Login(string returnUrl) { 789 | /// ViewBag.ReturnUrl = Url.Action("Foo"); // OK 790 | /// return RedirectToAction("Bar"); // Error: Cannot resolve action 791 | /// } 792 | /// 793 | [AttributeUsage(AttributeTargets.Parameter | AttributeTargets.Property)] 794 | public sealed class AspMvcActionSelectorAttribute : Attribute { } 795 | 796 | [AttributeUsage(AttributeTargets.Parameter | AttributeTargets.Property | AttributeTargets.Field)] 797 | public sealed class HtmlElementAttributesAttribute : Attribute 798 | { 799 | public HtmlElementAttributesAttribute() { } 800 | 801 | public HtmlElementAttributesAttribute([NotNull] string name) 802 | { 803 | Name = name; 804 | } 805 | 806 | [CanBeNull] public string Name { get; private set; } 807 | } 808 | 809 | [AttributeUsage(AttributeTargets.Parameter | AttributeTargets.Field | AttributeTargets.Property)] 810 | public sealed class HtmlAttributeValueAttribute : Attribute 811 | { 812 | public HtmlAttributeValueAttribute([NotNull] string name) 813 | { 814 | Name = name; 815 | } 816 | 817 | [NotNull] public string Name { get; private set; } 818 | } 819 | 820 | /// 821 | /// Razor attribute. Indicates that a parameter or a method is a Razor section. 822 | /// Use this attribute for custom wrappers similar to 823 | /// System.Web.WebPages.WebPageBase.RenderSection(String). 824 | /// 825 | [AttributeUsage(AttributeTargets.Parameter | AttributeTargets.Method)] 826 | public sealed class RazorSectionAttribute : Attribute { } 827 | 828 | /// 829 | /// Indicates how method, constructor invocation or property access 830 | /// over collection type affects content of the collection. 831 | /// 832 | [AttributeUsage(AttributeTargets.Method | AttributeTargets.Constructor | AttributeTargets.Property)] 833 | public sealed class CollectionAccessAttribute : Attribute 834 | { 835 | public CollectionAccessAttribute(CollectionAccessType collectionAccessType) 836 | { 837 | CollectionAccessType = collectionAccessType; 838 | } 839 | 840 | public CollectionAccessType CollectionAccessType { get; private set; } 841 | } 842 | 843 | [Flags] 844 | public enum CollectionAccessType 845 | { 846 | /// Method does not use or modify content of the collection. 847 | None = 0, 848 | /// Method only reads content of the collection but does not modify it. 849 | Read = 1, 850 | /// Method can change content of the collection but does not add new elements. 851 | ModifyExistingContent = 2, 852 | /// Method can add new elements to the collection. 853 | UpdatedContent = ModifyExistingContent | 4 854 | } 855 | 856 | /// 857 | /// Indicates that the marked method is assertion method, i.e. it halts control flow if 858 | /// one of the conditions is satisfied. To set the condition, mark one of the parameters with 859 | /// attribute. 860 | /// 861 | [AttributeUsage(AttributeTargets.Method)] 862 | public sealed class AssertionMethodAttribute : Attribute { } 863 | 864 | /// 865 | /// Indicates the condition parameter of the assertion method. The method itself should be 866 | /// marked by attribute. The mandatory argument of 867 | /// the attribute is the assertion type. 868 | /// 869 | [AttributeUsage(AttributeTargets.Parameter)] 870 | public sealed class AssertionConditionAttribute : Attribute 871 | { 872 | public AssertionConditionAttribute(AssertionConditionType conditionType) 873 | { 874 | ConditionType = conditionType; 875 | } 876 | 877 | public AssertionConditionType ConditionType { get; private set; } 878 | } 879 | 880 | /// 881 | /// Specifies assertion type. If the assertion method argument satisfies the condition, 882 | /// then the execution continues. Otherwise, execution is assumed to be halted. 883 | /// 884 | public enum AssertionConditionType 885 | { 886 | /// Marked parameter should be evaluated to true. 887 | IS_TRUE = 0, 888 | /// Marked parameter should be evaluated to false. 889 | IS_FALSE = 1, 890 | /// Marked parameter should be evaluated to null value. 891 | IS_NULL = 2, 892 | /// Marked parameter should be evaluated to not null value. 893 | IS_NOT_NULL = 3, 894 | } 895 | 896 | /// 897 | /// Indicates that the marked method unconditionally terminates control flow execution. 898 | /// For example, it could unconditionally throw exception. 899 | /// 900 | [Obsolete("Use [ContractAnnotation('=> halt')] instead")] 901 | [AttributeUsage(AttributeTargets.Method)] 902 | public sealed class TerminatesProgramAttribute : Attribute { } 903 | 904 | /// 905 | /// Indicates that method is pure LINQ method, with postponed enumeration (like Enumerable.Select, 906 | /// .Where). This annotation allows inference of [InstantHandle] annotation for parameters 907 | /// of delegate type by analyzing LINQ method chains. 908 | /// 909 | [AttributeUsage(AttributeTargets.Method)] 910 | public sealed class LinqTunnelAttribute : Attribute { } 911 | 912 | /// 913 | /// Indicates that IEnumerable, passed as parameter, is not enumerated. 914 | /// 915 | [AttributeUsage(AttributeTargets.Parameter)] 916 | public sealed class NoEnumerationAttribute : Attribute { } 917 | 918 | /// 919 | /// Indicates that parameter is regular expression pattern. 920 | /// 921 | [AttributeUsage(AttributeTargets.Parameter)] 922 | public sealed class RegexPatternAttribute : Attribute { } 923 | 924 | /// 925 | /// Prevents the Member Reordering feature from tossing members of the marked class. 926 | /// 927 | /// 928 | /// The attribute must be mentioned in your member reordering patterns 929 | /// 930 | [AttributeUsage( 931 | AttributeTargets.Class | AttributeTargets.Interface | AttributeTargets.Struct | AttributeTargets.Enum)] 932 | public sealed class NoReorderAttribute : Attribute { } 933 | 934 | /// 935 | /// XAML attribute. Indicates the type that has ItemsSource property and should be treated 936 | /// as ItemsControl-derived type, to enable inner items DataContext type resolve. 937 | /// 938 | [AttributeUsage(AttributeTargets.Class)] 939 | public sealed class XamlItemsControlAttribute : Attribute { } 940 | 941 | /// 942 | /// XAML attribute. Indicates the property of some BindingBase-derived type, that 943 | /// is used to bind some item of ItemsControl-derived type. This annotation will 944 | /// enable the DataContext type resolve for XAML bindings for such properties. 945 | /// 946 | /// 947 | /// Property should have the tree ancestor of the ItemsControl type or 948 | /// marked with the attribute. 949 | /// 950 | [AttributeUsage(AttributeTargets.Property)] 951 | public sealed class XamlItemBindingOfItemsControlAttribute : Attribute { } 952 | 953 | [AttributeUsage(AttributeTargets.Class, AllowMultiple = true)] 954 | public sealed class AspChildControlTypeAttribute : Attribute 955 | { 956 | public AspChildControlTypeAttribute([NotNull] string tagName, [NotNull] Type controlType) 957 | { 958 | TagName = tagName; 959 | ControlType = controlType; 960 | } 961 | 962 | [NotNull] public string TagName { get; private set; } 963 | 964 | [NotNull] public Type ControlType { get; private set; } 965 | } 966 | 967 | [AttributeUsage(AttributeTargets.Property | AttributeTargets.Method)] 968 | public sealed class AspDataFieldAttribute : Attribute { } 969 | 970 | [AttributeUsage(AttributeTargets.Property | AttributeTargets.Method)] 971 | public sealed class AspDataFieldsAttribute : Attribute { } 972 | 973 | [AttributeUsage(AttributeTargets.Property)] 974 | public sealed class AspMethodPropertyAttribute : Attribute { } 975 | 976 | [AttributeUsage(AttributeTargets.Class, AllowMultiple = true)] 977 | public sealed class AspRequiredAttributeAttribute : Attribute 978 | { 979 | public AspRequiredAttributeAttribute([NotNull] string attribute) 980 | { 981 | Attribute = attribute; 982 | } 983 | 984 | [NotNull] public string Attribute { get; private set; } 985 | } 986 | 987 | [AttributeUsage(AttributeTargets.Property)] 988 | public sealed class AspTypePropertyAttribute : Attribute 989 | { 990 | public bool CreateConstructorReferences { get; private set; } 991 | 992 | public AspTypePropertyAttribute(bool createConstructorReferences) 993 | { 994 | CreateConstructorReferences = createConstructorReferences; 995 | } 996 | } 997 | 998 | [AttributeUsage(AttributeTargets.Assembly, AllowMultiple = true)] 999 | public sealed class RazorImportNamespaceAttribute : Attribute 1000 | { 1001 | public RazorImportNamespaceAttribute([NotNull] string name) 1002 | { 1003 | Name = name; 1004 | } 1005 | 1006 | [NotNull] public string Name { get; private set; } 1007 | } 1008 | 1009 | [AttributeUsage(AttributeTargets.Assembly, AllowMultiple = true)] 1010 | public sealed class RazorInjectionAttribute : Attribute 1011 | { 1012 | public RazorInjectionAttribute([NotNull] string type, [NotNull] string fieldName) 1013 | { 1014 | Type = type; 1015 | FieldName = fieldName; 1016 | } 1017 | 1018 | [NotNull] public string Type { get; private set; } 1019 | 1020 | [NotNull] public string FieldName { get; private set; } 1021 | } 1022 | 1023 | [AttributeUsage(AttributeTargets.Assembly, AllowMultiple = true)] 1024 | public sealed class RazorDirectiveAttribute : Attribute 1025 | { 1026 | public RazorDirectiveAttribute([NotNull] string directive) 1027 | { 1028 | Directive = directive; 1029 | } 1030 | 1031 | [NotNull] public string Directive { get; private set; } 1032 | } 1033 | 1034 | [AttributeUsage(AttributeTargets.Method)] 1035 | public sealed class RazorHelperCommonAttribute : Attribute { } 1036 | 1037 | [AttributeUsage(AttributeTargets.Property)] 1038 | public sealed class RazorLayoutAttribute : Attribute { } 1039 | 1040 | [AttributeUsage(AttributeTargets.Method)] 1041 | public sealed class RazorWriteLiteralMethodAttribute : Attribute { } 1042 | 1043 | [AttributeUsage(AttributeTargets.Method)] 1044 | public sealed class RazorWriteMethodAttribute : Attribute { } 1045 | 1046 | [AttributeUsage(AttributeTargets.Parameter)] 1047 | public sealed class RazorWriteMethodParameterAttribute : Attribute { } 1048 | } -------------------------------------------------------------------------------- /WithGenericExternal/AssemblyInfo.cs: -------------------------------------------------------------------------------- 1 | using Generic; 2 | 3 | [assembly: Validar.ValidationTemplate(typeof(ValidationTemplate<>))] 4 | -------------------------------------------------------------------------------- /WithGenericExternal/Model.cs: -------------------------------------------------------------------------------- 1 | using System.ComponentModel; 2 | using System.Runtime.CompilerServices; 3 | using Validar; 4 | 5 | namespace WithGenericExternal 6 | { 7 | [InjectValidation] 8 | public sealed class MyModel : INotifyPropertyChanged 9 | { 10 | string property2; 11 | string property1; 12 | 13 | public string Property1 { 14 | get => property1; set 15 | { 16 | property1 = value; 17 | OnPropertyChanged(); 18 | } 19 | } 20 | 21 | public string Property2 { 22 | get => property2; set 23 | { 24 | property2 = value; 25 | OnPropertyChanged(); 26 | } 27 | } 28 | 29 | public event PropertyChangedEventHandler PropertyChanged; 30 | 31 | void OnPropertyChanged([CallerMemberName] string propertyName = null) 32 | { 33 | PropertyChanged?.Invoke(this, new PropertyChangedEventArgs(propertyName)); 34 | } 35 | } 36 | } -------------------------------------------------------------------------------- /WithGenericExternal/ModelValidator.cs: -------------------------------------------------------------------------------- 1 | using FluentValidation; 2 | 3 | namespace WithGenericExternal 4 | { 5 | public class MyModelValidator : AbstractValidator 6 | { 7 | public MyModelValidator() 8 | { 9 | RuleFor(x => x.Property1) 10 | .NotEmpty() 11 | .WithMessage("'Property1' message."); 12 | RuleFor(x => x.Property2) 13 | .NotEmpty() 14 | .WithMessage("'Property2' message."); 15 | } 16 | } 17 | } -------------------------------------------------------------------------------- /WithGenericExternal/ModelWithImplementation.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | using System.Collections; 3 | using System.ComponentModel; 4 | using System.Runtime.CompilerServices; 5 | using Generic; 6 | using Validar; 7 | 8 | namespace WithGenericExternal 9 | { 10 | [InjectValidation] 11 | public class ModelWithImplementation : 12 | INotifyPropertyChanged, 13 | IDataErrorInfo, 14 | INotifyDataErrorInfo 15 | { 16 | ValidationTemplate validationTemplate; 17 | public ModelWithImplementation() 18 | { 19 | validationTemplate = new ValidationTemplate(this); 20 | } 21 | 22 | public string this[string columnName] => validationTemplate[columnName]; 23 | 24 | public IEnumerable GetErrors(string propertyName) 25 | { 26 | return validationTemplate.GetErrors(propertyName); 27 | } 28 | public bool HasErrors => validationTemplate.HasErrors; 29 | 30 | public event EventHandler ErrorsChanged 31 | { 32 | add => validationTemplate.ErrorsChanged += value; 33 | remove => validationTemplate.ErrorsChanged -= value; 34 | } 35 | 36 | public string Error => validationTemplate.Error; 37 | 38 | string property1; 39 | 40 | public string Property1 41 | { 42 | get => property1; 43 | set 44 | { 45 | if (value != property1) 46 | { 47 | property1 = value; 48 | OnPropertyChanged(); 49 | } 50 | } 51 | } 52 | 53 | string property2; 54 | 55 | public string Property2 56 | { 57 | get => property2; 58 | set 59 | { 60 | if (value != property2) 61 | { 62 | property2 = value; 63 | OnPropertyChanged(); 64 | } 65 | } 66 | } 67 | 68 | void OnPropertyChanged([CallerMemberName] string propertyName = null) 69 | { 70 | PropertyChanged?.Invoke(this, new PropertyChangedEventArgs(propertyName)); 71 | } 72 | 73 | public event PropertyChangedEventHandler PropertyChanged; 74 | } 75 | } -------------------------------------------------------------------------------- /WithGenericExternal/ModelWithImplementationValidator.cs: -------------------------------------------------------------------------------- 1 | using FluentValidation; 2 | 3 | namespace WithGenericExternal 4 | { 5 | public class ModelWithImplementationValidator : 6 | AbstractValidator 7 | { 8 | public ModelWithImplementationValidator() 9 | { 10 | RuleFor(x => x.Property1) 11 | .NotEmpty() 12 | .WithMessage("'Property1' message."); 13 | RuleFor(x => x.Property2) 14 | .NotEmpty() 15 | .WithMessage("'Property2' message."); 16 | } 17 | } 18 | } -------------------------------------------------------------------------------- /WithGenericExternal/WithGenericExternal.csproj: -------------------------------------------------------------------------------- 1 |  2 | 3 | 4 | net472;netcoreapp2.0 5 | true 6 | 7 | 8 | 9 | 10 | 11 | 12 | -------------------------------------------------------------------------------- /WithGenericInternal/Annotations.cs: -------------------------------------------------------------------------------- 1 | /* MIT License 2 | 3 | Copyright (c) 2016 JetBrains http://www.jetbrains.com 4 | 5 | Permission is hereby granted, free of charge, to any person obtaining a copy 6 | of this software and associated documentation files (the "Software"), to deal 7 | in the Software without restriction, including without limitation the rights 8 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 9 | copies of the Software, and to permit persons to whom the Software is 10 | furnished to do so, subject to the following conditions: 11 | 12 | The above copyright notice and this permission notice shall be included in all 13 | copies or substantial portions of the Software. 14 | 15 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 16 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 17 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 18 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 19 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 20 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE 21 | SOFTWARE. */ 22 | 23 | using System; 24 | 25 | #pragma warning disable 1591 26 | // ReSharper disable UnusedMember.Global 27 | // ReSharper disable MemberCanBePrivate.Global 28 | // ReSharper disable UnusedAutoPropertyAccessor.Global 29 | // ReSharper disable IntroduceOptionalParameters.Global 30 | // ReSharper disable MemberCanBeProtected.Global 31 | // ReSharper disable InconsistentNaming 32 | 33 | namespace WithGenericInternal.Annotations 34 | { 35 | /// 36 | /// Indicates that the value of the marked element could be null sometimes, 37 | /// so the check for null is necessary before its usage. 38 | /// 39 | /// 40 | /// [CanBeNull] object Test() => null; 41 | /// 42 | /// void UseTest() { 43 | /// var p = Test(); 44 | /// var s = p.ToString(); // Warning: Possible 'System.NullReferenceException' 45 | /// } 46 | /// 47 | [AttributeUsage( 48 | AttributeTargets.Method | AttributeTargets.Parameter | AttributeTargets.Property | 49 | AttributeTargets.Delegate | AttributeTargets.Field | AttributeTargets.Event | 50 | AttributeTargets.Class | AttributeTargets.Interface | AttributeTargets.GenericParameter)] 51 | public sealed class CanBeNullAttribute : Attribute { } 52 | 53 | /// 54 | /// Indicates that the value of the marked element could never be null. 55 | /// 56 | /// 57 | /// [NotNull] object Foo() { 58 | /// return null; // Warning: Possible 'null' assignment 59 | /// } 60 | /// 61 | [AttributeUsage( 62 | AttributeTargets.Method | AttributeTargets.Parameter | AttributeTargets.Property | 63 | AttributeTargets.Delegate | AttributeTargets.Field | AttributeTargets.Event | 64 | AttributeTargets.Class | AttributeTargets.Interface | AttributeTargets.GenericParameter)] 65 | public sealed class NotNullAttribute : Attribute { } 66 | 67 | /// 68 | /// Can be appplied to symbols of types derived from IEnumerable as well as to symbols of Task 69 | /// and Lazy classes to indicate that the value of a collection item, of the Task.Result property 70 | /// or of the Lazy.Value property can never be null. 71 | /// 72 | [AttributeUsage( 73 | AttributeTargets.Method | AttributeTargets.Parameter | AttributeTargets.Property | 74 | AttributeTargets.Delegate | AttributeTargets.Field)] 75 | public sealed class ItemNotNullAttribute : Attribute { } 76 | 77 | /// 78 | /// Can be appplied to symbols of types derived from IEnumerable as well as to symbols of Task 79 | /// and Lazy classes to indicate that the value of a collection item, of the Task.Result property 80 | /// or of the Lazy.Value property can be null. 81 | /// 82 | [AttributeUsage( 83 | AttributeTargets.Method | AttributeTargets.Parameter | AttributeTargets.Property | 84 | AttributeTargets.Delegate | AttributeTargets.Field)] 85 | public sealed class ItemCanBeNullAttribute : Attribute { } 86 | 87 | /// 88 | /// Indicates that the marked method builds string by format pattern and (optional) arguments. 89 | /// Parameter, which contains format string, should be given in constructor. The format string 90 | /// should be in -like form. 91 | /// 92 | /// 93 | /// [StringFormatMethod("message")] 94 | /// void ShowError(string message, params object[] args) { /* do something */ } 95 | /// 96 | /// void Foo() { 97 | /// ShowError("Failed: {0}"); // Warning: Non-existing argument in format string 98 | /// } 99 | /// 100 | [AttributeUsage( 101 | AttributeTargets.Constructor | AttributeTargets.Method | 102 | AttributeTargets.Property | AttributeTargets.Delegate)] 103 | public sealed class StringFormatMethodAttribute : Attribute 104 | { 105 | /// 106 | /// Specifies which parameter of an annotated method should be treated as format-string 107 | /// 108 | public StringFormatMethodAttribute([NotNull] string formatParameterName) 109 | { 110 | FormatParameterName = formatParameterName; 111 | } 112 | 113 | [NotNull] public string FormatParameterName { get; private set; } 114 | } 115 | 116 | /// 117 | /// For a parameter that is expected to be one of the limited set of values. 118 | /// Specify fields of which type should be used as values for this parameter. 119 | /// 120 | [AttributeUsage( 121 | AttributeTargets.Parameter | AttributeTargets.Property | AttributeTargets.Field, 122 | AllowMultiple = true)] 123 | public sealed class ValueProviderAttribute : Attribute 124 | { 125 | public ValueProviderAttribute([NotNull] string name) 126 | { 127 | Name = name; 128 | } 129 | 130 | [NotNull] public string Name { get; private set; } 131 | } 132 | 133 | /// 134 | /// Indicates that the function argument should be string literal and match one 135 | /// of the parameters of the caller function. For example, ReSharper annotates 136 | /// the parameter of . 137 | /// 138 | /// 139 | /// void Foo(string param) { 140 | /// if (param == null) 141 | /// throw new ArgumentNullException("par"); // Warning: Cannot resolve symbol 142 | /// } 143 | /// 144 | [AttributeUsage(AttributeTargets.Parameter)] 145 | public sealed class InvokerParameterNameAttribute : Attribute { } 146 | 147 | /// 148 | /// Indicates that the method is contained in a type that implements 149 | /// System.ComponentModel.INotifyPropertyChanged interface and this method 150 | /// is used to notify that some property value changed. 151 | /// 152 | /// 153 | /// The method should be non-static and conform to one of the supported signatures: 154 | /// 155 | /// NotifyChanged(string) 156 | /// NotifyChanged(params string[]) 157 | /// NotifyChanged{T}(Expression{Func{T}}) 158 | /// NotifyChanged{T,U}(Expression{Func{T,U}}) 159 | /// SetProperty{T}(ref T, T, string) 160 | /// 161 | /// 162 | /// 163 | /// public class Foo : INotifyPropertyChanged { 164 | /// public event PropertyChangedEventHandler PropertyChanged; 165 | /// 166 | /// [NotifyPropertyChangedInvocator] 167 | /// protected virtual void NotifyChanged(string propertyName) { ... } 168 | /// 169 | /// string _name; 170 | /// 171 | /// public string Name { 172 | /// get { return _name; } 173 | /// set { _name = value; NotifyChanged("LastName"); /* Warning */ } 174 | /// } 175 | /// } 176 | /// 177 | /// Examples of generated notifications: 178 | /// 179 | /// NotifyChanged("Property") 180 | /// NotifyChanged(() => Property) 181 | /// NotifyChanged((VM x) => x.Property) 182 | /// SetProperty(ref myField, value, "Property") 183 | /// 184 | /// 185 | [AttributeUsage(AttributeTargets.Method)] 186 | public sealed class NotifyPropertyChangedInvocatorAttribute : Attribute 187 | { 188 | public NotifyPropertyChangedInvocatorAttribute() { } 189 | public NotifyPropertyChangedInvocatorAttribute([NotNull] string parameterName) 190 | { 191 | ParameterName = parameterName; 192 | } 193 | 194 | [CanBeNull] public string ParameterName { get; private set; } 195 | } 196 | 197 | /// 198 | /// Describes dependency between method input and output. 199 | /// 200 | /// 201 | ///

Function Definition Table syntax:

202 | /// 203 | /// FDT ::= FDTRow [;FDTRow]* 204 | /// FDTRow ::= Input => Output | Output <= Input 205 | /// Input ::= ParameterName: Value [, Input]* 206 | /// Output ::= [ParameterName: Value]* {halt|stop|void|nothing|Value} 207 | /// Value ::= true | false | null | notnull | canbenull 208 | /// 209 | /// If method has single input parameter, it's name could be omitted.
210 | /// Using halt (or void/nothing, which is the same) for method output 211 | /// means that the methos doesn't return normally (throws or terminates the process).
212 | /// Value canbenull is only applicable for output parameters.
213 | /// You can use multiple [ContractAnnotation] for each FDT row, or use single attribute 214 | /// with rows separated by semicolon. There is no notion of order rows, all rows are checked 215 | /// for applicability and applied per each program state tracked by R# analysis.
216 | ///
217 | /// 218 | /// 219 | /// [ContractAnnotation("=> halt")] 220 | /// public void TerminationMethod() 221 | /// 222 | /// 223 | /// [ContractAnnotation("halt <= condition: false")] 224 | /// public void Assert(bool condition, string text) // regular assertion method 225 | /// 226 | /// 227 | /// [ContractAnnotation("s:null => true")] 228 | /// public bool IsNullOrEmpty(string s) // string.IsNullOrEmpty() 229 | /// 230 | /// 231 | /// // A method that returns null if the parameter is null, 232 | /// // and not null if the parameter is not null 233 | /// [ContractAnnotation("null => null; notnull => notnull")] 234 | /// public object Transform(object data) 235 | /// 236 | /// 237 | /// [ContractAnnotation("=> true, result: notnull; => false, result: null")] 238 | /// public bool TryParse(string s, out Person result) 239 | /// 240 | /// 241 | [AttributeUsage(AttributeTargets.Method, AllowMultiple = true)] 242 | public sealed class ContractAnnotationAttribute : Attribute 243 | { 244 | public ContractAnnotationAttribute([NotNull] string contract) 245 | : this(contract, false) { } 246 | 247 | public ContractAnnotationAttribute([NotNull] string contract, bool forceFullStates) 248 | { 249 | Contract = contract; 250 | ForceFullStates = forceFullStates; 251 | } 252 | 253 | [NotNull] public string Contract { get; private set; } 254 | 255 | public bool ForceFullStates { get; private set; } 256 | } 257 | 258 | /// 259 | /// Indicates that marked element should be localized or not. 260 | /// 261 | /// 262 | /// [LocalizationRequiredAttribute(true)] 263 | /// class Foo { 264 | /// string str = "my string"; // Warning: Localizable string 265 | /// } 266 | /// 267 | [AttributeUsage(AttributeTargets.All)] 268 | public sealed class LocalizationRequiredAttribute : Attribute 269 | { 270 | public LocalizationRequiredAttribute() : this(true) { } 271 | 272 | public LocalizationRequiredAttribute(bool required) 273 | { 274 | Required = required; 275 | } 276 | 277 | public bool Required { get; private set; } 278 | } 279 | 280 | /// 281 | /// Indicates that the value of the marked type (or its derivatives) 282 | /// cannot be compared using '==' or '!=' operators and Equals() 283 | /// should be used instead. However, using '==' or '!=' for comparison 284 | /// with null is always permitted. 285 | /// 286 | /// 287 | /// [CannotApplyEqualityOperator] 288 | /// class NoEquality { } 289 | /// 290 | /// class UsesNoEquality { 291 | /// void Test() { 292 | /// var ca1 = new NoEquality(); 293 | /// var ca2 = new NoEquality(); 294 | /// if (ca1 != null) { // OK 295 | /// bool condition = ca1 == ca2; // Warning 296 | /// } 297 | /// } 298 | /// } 299 | /// 300 | [AttributeUsage(AttributeTargets.Interface | AttributeTargets.Class | AttributeTargets.Struct)] 301 | public sealed class CannotApplyEqualityOperatorAttribute : Attribute { } 302 | 303 | /// 304 | /// When applied to a target attribute, specifies a requirement for any type marked 305 | /// with the target attribute to implement or inherit specific type or types. 306 | /// 307 | /// 308 | /// [BaseTypeRequired(typeof(IComponent)] // Specify requirement 309 | /// class ComponentAttribute : Attribute { } 310 | /// 311 | /// [Component] // ComponentAttribute requires implementing IComponent interface 312 | /// class MyComponent : IComponent { } 313 | /// 314 | [AttributeUsage(AttributeTargets.Class, AllowMultiple = true)] 315 | [BaseTypeRequired(typeof(Attribute))] 316 | public sealed class BaseTypeRequiredAttribute : Attribute 317 | { 318 | public BaseTypeRequiredAttribute([NotNull] Type baseType) 319 | { 320 | BaseType = baseType; 321 | } 322 | 323 | [NotNull] public Type BaseType { get; private set; } 324 | } 325 | 326 | /// 327 | /// Indicates that the marked symbol is used implicitly (e.g. via reflection, in external library), 328 | /// so this symbol will not be marked as unused (as well as by other usage inspections). 329 | /// 330 | [AttributeUsage(AttributeTargets.All)] 331 | public sealed class UsedImplicitlyAttribute : Attribute 332 | { 333 | public UsedImplicitlyAttribute() 334 | : this(ImplicitUseKindFlags.Default, ImplicitUseTargetFlags.Default) { } 335 | 336 | public UsedImplicitlyAttribute(ImplicitUseKindFlags useKindFlags) 337 | : this(useKindFlags, ImplicitUseTargetFlags.Default) { } 338 | 339 | public UsedImplicitlyAttribute(ImplicitUseTargetFlags targetFlags) 340 | : this(ImplicitUseKindFlags.Default, targetFlags) { } 341 | 342 | public UsedImplicitlyAttribute(ImplicitUseKindFlags useKindFlags, ImplicitUseTargetFlags targetFlags) 343 | { 344 | UseKindFlags = useKindFlags; 345 | TargetFlags = targetFlags; 346 | } 347 | 348 | public ImplicitUseKindFlags UseKindFlags { get; private set; } 349 | 350 | public ImplicitUseTargetFlags TargetFlags { get; private set; } 351 | } 352 | 353 | /// 354 | /// Should be used on attributes and causes ReSharper to not mark symbols marked with such attributes 355 | /// as unused (as well as by other usage inspections) 356 | /// 357 | [AttributeUsage(AttributeTargets.Class | AttributeTargets.GenericParameter)] 358 | public sealed class MeansImplicitUseAttribute : Attribute 359 | { 360 | public MeansImplicitUseAttribute() 361 | : this(ImplicitUseKindFlags.Default, ImplicitUseTargetFlags.Default) { } 362 | 363 | public MeansImplicitUseAttribute(ImplicitUseKindFlags useKindFlags) 364 | : this(useKindFlags, ImplicitUseTargetFlags.Default) { } 365 | 366 | public MeansImplicitUseAttribute(ImplicitUseTargetFlags targetFlags) 367 | : this(ImplicitUseKindFlags.Default, targetFlags) { } 368 | 369 | public MeansImplicitUseAttribute(ImplicitUseKindFlags useKindFlags, ImplicitUseTargetFlags targetFlags) 370 | { 371 | UseKindFlags = useKindFlags; 372 | TargetFlags = targetFlags; 373 | } 374 | 375 | [UsedImplicitly] public ImplicitUseKindFlags UseKindFlags { get; private set; } 376 | 377 | [UsedImplicitly] public ImplicitUseTargetFlags TargetFlags { get; private set; } 378 | } 379 | 380 | [Flags] 381 | public enum ImplicitUseKindFlags 382 | { 383 | Default = Access | Assign | InstantiatedWithFixedConstructorSignature, 384 | /// Only entity marked with attribute considered used. 385 | Access = 1, 386 | /// Indicates implicit assignment to a member. 387 | Assign = 2, 388 | /// 389 | /// Indicates implicit instantiation of a type with fixed constructor signature. 390 | /// That means any unused constructor parameters won't be reported as such. 391 | /// 392 | InstantiatedWithFixedConstructorSignature = 4, 393 | /// Indicates implicit instantiation of a type. 394 | InstantiatedNoFixedConstructorSignature = 8, 395 | } 396 | 397 | /// 398 | /// Specify what is considered used implicitly when marked 399 | /// with or . 400 | /// 401 | [Flags] 402 | public enum ImplicitUseTargetFlags 403 | { 404 | Default = Itself, 405 | Itself = 1, 406 | /// Members of entity marked with attribute are considered used. 407 | Members = 2, 408 | /// Entity marked with attribute and all its members considered used. 409 | WithMembers = Itself | Members 410 | } 411 | 412 | /// 413 | /// This attribute is intended to mark publicly available API 414 | /// which should not be removed and so is treated as used. 415 | /// 416 | [MeansImplicitUse(ImplicitUseTargetFlags.WithMembers)] 417 | public sealed class PublicAPIAttribute : Attribute 418 | { 419 | public PublicAPIAttribute() { } 420 | 421 | public PublicAPIAttribute([NotNull] string comment) 422 | { 423 | Comment = comment; 424 | } 425 | 426 | [CanBeNull] public string Comment { get; private set; } 427 | } 428 | 429 | /// 430 | /// Tells code analysis engine if the parameter is completely handled when the invoked method is on stack. 431 | /// If the parameter is a delegate, indicates that delegate is executed while the method is executed. 432 | /// If the parameter is an enumerable, indicates that it is enumerated while the method is executed. 433 | /// 434 | [AttributeUsage(AttributeTargets.Parameter)] 435 | public sealed class InstantHandleAttribute : Attribute { } 436 | 437 | /// 438 | /// Indicates that a method does not make any observable state changes. 439 | /// The same as System.Diagnostics.Contracts.PureAttribute. 440 | /// 441 | /// 442 | /// [Pure] int Multiply(int x, int y) => x * y; 443 | /// 444 | /// void M() { 445 | /// Multiply(123, 42); // Waring: Return value of pure method is not used 446 | /// } 447 | /// 448 | [AttributeUsage(AttributeTargets.Method)] 449 | public sealed class PureAttribute : Attribute { } 450 | 451 | /// 452 | /// Indicates that the return value of method invocation must be used. 453 | /// 454 | [AttributeUsage(AttributeTargets.Method)] 455 | public sealed class MustUseReturnValueAttribute : Attribute 456 | { 457 | public MustUseReturnValueAttribute() { } 458 | 459 | public MustUseReturnValueAttribute([NotNull] string justification) 460 | { 461 | Justification = justification; 462 | } 463 | 464 | [CanBeNull] public string Justification { get; private set; } 465 | } 466 | 467 | /// 468 | /// Indicates the type member or parameter of some type, that should be used instead of all other ways 469 | /// to get the value that type. This annotation is useful when you have some "context" value evaluated 470 | /// and stored somewhere, meaning that all other ways to get this value must be consolidated with existing one. 471 | /// 472 | /// 473 | /// class Foo { 474 | /// [ProvidesContext] IBarService _barService = ...; 475 | /// 476 | /// void ProcessNode(INode node) { 477 | /// DoSomething(node, node.GetGlobalServices().Bar); 478 | /// // ^ Warning: use value of '_barService' field 479 | /// } 480 | /// } 481 | /// 482 | [AttributeUsage( 483 | AttributeTargets.Field | AttributeTargets.Property | AttributeTargets.Parameter | AttributeTargets.Method | 484 | AttributeTargets.Class | AttributeTargets.Interface | AttributeTargets.Struct | AttributeTargets.GenericParameter)] 485 | public sealed class ProvidesContextAttribute : Attribute { } 486 | 487 | /// 488 | /// Indicates that a parameter is a path to a file or a folder within a web project. 489 | /// Path can be relative or absolute, starting from web root (~). 490 | /// 491 | [AttributeUsage(AttributeTargets.Parameter)] 492 | public sealed class PathReferenceAttribute : Attribute 493 | { 494 | public PathReferenceAttribute() { } 495 | 496 | public PathReferenceAttribute([NotNull, PathReference] string basePath) 497 | { 498 | BasePath = basePath; 499 | } 500 | 501 | [CanBeNull] public string BasePath { get; private set; } 502 | } 503 | 504 | /// 505 | /// An extension method marked with this attribute is processed by ReSharper code completion 506 | /// as a 'Source Template'. When extension method is completed over some expression, it's source code 507 | /// is automatically expanded like a template at call site. 508 | /// 509 | /// 510 | /// Template method body can contain valid source code and/or special comments starting with '$'. 511 | /// Text inside these comments is added as source code when the template is applied. Template parameters 512 | /// can be used either as additional method parameters or as identifiers wrapped in two '$' signs. 513 | /// Use the attribute to specify macros for parameters. 514 | /// 515 | /// 516 | /// In this example, the 'forEach' method is a source template available over all values 517 | /// of enumerable types, producing ordinary C# 'foreach' statement and placing caret inside block: 518 | /// 519 | /// [SourceTemplate] 520 | /// public static void forEach<T>(this IEnumerable<T> xs) { 521 | /// foreach (var x in xs) { 522 | /// //$ $END$ 523 | /// } 524 | /// } 525 | /// 526 | /// 527 | [AttributeUsage(AttributeTargets.Method)] 528 | public sealed class SourceTemplateAttribute : Attribute { } 529 | 530 | /// 531 | /// Allows specifying a macro for a parameter of a source template. 532 | /// 533 | /// 534 | /// You can apply the attribute on the whole method or on any of its additional parameters. The macro expression 535 | /// is defined in the property. When applied on a method, the target 536 | /// template parameter is defined in the property. To apply the macro silently 537 | /// for the parameter, set the property value = -1. 538 | /// 539 | /// 540 | /// Applying the attribute on a source template method: 541 | /// 542 | /// [SourceTemplate, Macro(Target = "item", Expression = "suggestVariableName()")] 543 | /// public static void forEach<T>(this IEnumerable<T> collection) { 544 | /// foreach (var item in collection) { 545 | /// //$ $END$ 546 | /// } 547 | /// } 548 | /// 549 | /// Applying the attribute on a template method parameter: 550 | /// 551 | /// [SourceTemplate] 552 | /// public static void something(this Entity x, [Macro(Expression = "guid()", Editable = -1)] string newguid) { 553 | /// /*$ var $x$Id = "$newguid$" + x.ToString(); 554 | /// x.DoSomething($x$Id); */ 555 | /// } 556 | /// 557 | /// 558 | [AttributeUsage(AttributeTargets.Parameter | AttributeTargets.Method, AllowMultiple = true)] 559 | public sealed class MacroAttribute : Attribute 560 | { 561 | /// 562 | /// Allows specifying a macro that will be executed for a source template 563 | /// parameter when the template is expanded. 564 | /// 565 | [CanBeNull] public string Expression { get; set; } 566 | 567 | /// 568 | /// Allows specifying which occurrence of the target parameter becomes editable when the template is deployed. 569 | /// 570 | /// 571 | /// If the target parameter is used several times in the template, only one occurrence becomes editable; 572 | /// other occurrences are changed synchronously. To specify the zero-based index of the editable occurrence, 573 | /// use values >= 0. To make the parameter non-editable when the template is expanded, use -1. 574 | /// > 575 | public int Editable { get; set; } 576 | 577 | /// 578 | /// Identifies the target parameter of a source template if the 579 | /// is applied on a template method. 580 | /// 581 | [CanBeNull] public string Target { get; set; } 582 | } 583 | 584 | [AttributeUsage(AttributeTargets.Assembly | AttributeTargets.Field | AttributeTargets.Property, AllowMultiple = true)] 585 | public sealed class AspMvcAreaMasterLocationFormatAttribute : Attribute 586 | { 587 | public AspMvcAreaMasterLocationFormatAttribute([NotNull] string format) 588 | { 589 | Format = format; 590 | } 591 | 592 | [NotNull] public string Format { get; private set; } 593 | } 594 | 595 | [AttributeUsage(AttributeTargets.Assembly | AttributeTargets.Field | AttributeTargets.Property, AllowMultiple = true)] 596 | public sealed class AspMvcAreaPartialViewLocationFormatAttribute : Attribute 597 | { 598 | public AspMvcAreaPartialViewLocationFormatAttribute([NotNull] string format) 599 | { 600 | Format = format; 601 | } 602 | 603 | [NotNull] public string Format { get; private set; } 604 | } 605 | 606 | [AttributeUsage(AttributeTargets.Assembly | AttributeTargets.Field | AttributeTargets.Property, AllowMultiple = true)] 607 | public sealed class AspMvcAreaViewLocationFormatAttribute : Attribute 608 | { 609 | public AspMvcAreaViewLocationFormatAttribute([NotNull] string format) 610 | { 611 | Format = format; 612 | } 613 | 614 | [NotNull] public string Format { get; private set; } 615 | } 616 | 617 | [AttributeUsage(AttributeTargets.Assembly | AttributeTargets.Field | AttributeTargets.Property, AllowMultiple = true)] 618 | public sealed class AspMvcMasterLocationFormatAttribute : Attribute 619 | { 620 | public AspMvcMasterLocationFormatAttribute([NotNull] string format) 621 | { 622 | Format = format; 623 | } 624 | 625 | [NotNull] public string Format { get; private set; } 626 | } 627 | 628 | [AttributeUsage(AttributeTargets.Assembly | AttributeTargets.Field | AttributeTargets.Property, AllowMultiple = true)] 629 | public sealed class AspMvcPartialViewLocationFormatAttribute : Attribute 630 | { 631 | public AspMvcPartialViewLocationFormatAttribute([NotNull] string format) 632 | { 633 | Format = format; 634 | } 635 | 636 | [NotNull] public string Format { get; private set; } 637 | } 638 | 639 | [AttributeUsage(AttributeTargets.Assembly | AttributeTargets.Field | AttributeTargets.Property, AllowMultiple = true)] 640 | public sealed class AspMvcViewLocationFormatAttribute : Attribute 641 | { 642 | public AspMvcViewLocationFormatAttribute([NotNull] string format) 643 | { 644 | Format = format; 645 | } 646 | 647 | [NotNull] public string Format { get; private set; } 648 | } 649 | 650 | /// 651 | /// ASP.NET MVC attribute. If applied to a parameter, indicates that the parameter 652 | /// is an MVC action. If applied to a method, the MVC action name is calculated 653 | /// implicitly from the context. Use this attribute for custom wrappers similar to 654 | /// System.Web.Mvc.Html.ChildActionExtensions.RenderAction(HtmlHelper, String). 655 | /// 656 | [AttributeUsage(AttributeTargets.Parameter | AttributeTargets.Method)] 657 | public sealed class AspMvcActionAttribute : Attribute 658 | { 659 | public AspMvcActionAttribute() { } 660 | 661 | public AspMvcActionAttribute([NotNull] string anonymousProperty) 662 | { 663 | AnonymousProperty = anonymousProperty; 664 | } 665 | 666 | [CanBeNull] public string AnonymousProperty { get; private set; } 667 | } 668 | 669 | /// 670 | /// ASP.NET MVC attribute. Indicates that a parameter is an MVC area. 671 | /// Use this attribute for custom wrappers similar to 672 | /// System.Web.Mvc.Html.ChildActionExtensions.RenderAction(HtmlHelper, String). 673 | /// 674 | [AttributeUsage(AttributeTargets.Parameter)] 675 | public sealed class AspMvcAreaAttribute : Attribute 676 | { 677 | public AspMvcAreaAttribute() { } 678 | 679 | public AspMvcAreaAttribute([NotNull] string anonymousProperty) 680 | { 681 | AnonymousProperty = anonymousProperty; 682 | } 683 | 684 | [CanBeNull] public string AnonymousProperty { get; private set; } 685 | } 686 | 687 | /// 688 | /// ASP.NET MVC attribute. If applied to a parameter, indicates that the parameter is 689 | /// an MVC controller. If applied to a method, the MVC controller name is calculated 690 | /// implicitly from the context. Use this attribute for custom wrappers similar to 691 | /// System.Web.Mvc.Html.ChildActionExtensions.RenderAction(HtmlHelper, String, String). 692 | /// 693 | [AttributeUsage(AttributeTargets.Parameter | AttributeTargets.Method)] 694 | public sealed class AspMvcControllerAttribute : Attribute 695 | { 696 | public AspMvcControllerAttribute() { } 697 | 698 | public AspMvcControllerAttribute([NotNull] string anonymousProperty) 699 | { 700 | AnonymousProperty = anonymousProperty; 701 | } 702 | 703 | [CanBeNull] public string AnonymousProperty { get; private set; } 704 | } 705 | 706 | /// 707 | /// ASP.NET MVC attribute. Indicates that a parameter is an MVC Master. Use this attribute 708 | /// for custom wrappers similar to System.Web.Mvc.Controller.View(String, String). 709 | /// 710 | [AttributeUsage(AttributeTargets.Parameter)] 711 | public sealed class AspMvcMasterAttribute : Attribute { } 712 | 713 | /// 714 | /// ASP.NET MVC attribute. Indicates that a parameter is an MVC model type. Use this attribute 715 | /// for custom wrappers similar to System.Web.Mvc.Controller.View(String, Object). 716 | /// 717 | [AttributeUsage(AttributeTargets.Parameter)] 718 | public sealed class AspMvcModelTypeAttribute : Attribute { } 719 | 720 | /// 721 | /// ASP.NET MVC attribute. If applied to a parameter, indicates that the parameter is an MVC 722 | /// partial view. If applied to a method, the MVC partial view name is calculated implicitly 723 | /// from the context. Use this attribute for custom wrappers similar to 724 | /// System.Web.Mvc.Html.RenderPartialExtensions.RenderPartial(HtmlHelper, String). 725 | /// 726 | [AttributeUsage(AttributeTargets.Parameter | AttributeTargets.Method)] 727 | public sealed class AspMvcPartialViewAttribute : Attribute { } 728 | 729 | /// 730 | /// ASP.NET MVC attribute. Allows disabling inspections for MVC views within a class or a method. 731 | /// 732 | [AttributeUsage(AttributeTargets.Class | AttributeTargets.Method)] 733 | public sealed class AspMvcSuppressViewErrorAttribute : Attribute { } 734 | 735 | /// 736 | /// ASP.NET MVC attribute. Indicates that a parameter is an MVC display template. 737 | /// Use this attribute for custom wrappers similar to 738 | /// System.Web.Mvc.Html.DisplayExtensions.DisplayForModel(HtmlHelper, String). 739 | /// 740 | [AttributeUsage(AttributeTargets.Parameter)] 741 | public sealed class AspMvcDisplayTemplateAttribute : Attribute { } 742 | 743 | /// 744 | /// ASP.NET MVC attribute. Indicates that a parameter is an MVC editor template. 745 | /// Use this attribute for custom wrappers similar to 746 | /// System.Web.Mvc.Html.EditorExtensions.EditorForModel(HtmlHelper, String). 747 | /// 748 | [AttributeUsage(AttributeTargets.Parameter)] 749 | public sealed class AspMvcEditorTemplateAttribute : Attribute { } 750 | 751 | /// 752 | /// ASP.NET MVC attribute. Indicates that a parameter is an MVC template. 753 | /// Use this attribute for custom wrappers similar to 754 | /// System.ComponentModel.DataAnnotations.UIHintAttribute(System.String). 755 | /// 756 | [AttributeUsage(AttributeTargets.Parameter)] 757 | public sealed class AspMvcTemplateAttribute : Attribute { } 758 | 759 | /// 760 | /// ASP.NET MVC attribute. If applied to a parameter, indicates that the parameter 761 | /// is an MVC view component. If applied to a method, the MVC view name is calculated implicitly 762 | /// from the context. Use this attribute for custom wrappers similar to 763 | /// System.Web.Mvc.Controller.View(Object). 764 | /// 765 | [AttributeUsage(AttributeTargets.Parameter | AttributeTargets.Method)] 766 | public sealed class AspMvcViewAttribute : Attribute { } 767 | 768 | /// 769 | /// ASP.NET MVC attribute. If applied to a parameter, indicates that the parameter 770 | /// is an MVC view component name. 771 | /// 772 | [AttributeUsage(AttributeTargets.Parameter)] 773 | public sealed class AspMvcViewComponentAttribute : Attribute { } 774 | 775 | /// 776 | /// ASP.NET MVC attribute. If applied to a parameter, indicates that the parameter 777 | /// is an MVC view component view. If applied to a method, the MVC view component view name is default. 778 | /// 779 | [AttributeUsage(AttributeTargets.Parameter | AttributeTargets.Method)] 780 | public sealed class AspMvcViewComponentViewAttribute : Attribute { } 781 | 782 | /// 783 | /// ASP.NET MVC attribute. When applied to a parameter of an attribute, 784 | /// indicates that this parameter is an MVC action name. 785 | /// 786 | /// 787 | /// [ActionName("Foo")] 788 | /// public ActionResult Login(string returnUrl) { 789 | /// ViewBag.ReturnUrl = Url.Action("Foo"); // OK 790 | /// return RedirectToAction("Bar"); // Error: Cannot resolve action 791 | /// } 792 | /// 793 | [AttributeUsage(AttributeTargets.Parameter | AttributeTargets.Property)] 794 | public sealed class AspMvcActionSelectorAttribute : Attribute { } 795 | 796 | [AttributeUsage(AttributeTargets.Parameter | AttributeTargets.Property | AttributeTargets.Field)] 797 | public sealed class HtmlElementAttributesAttribute : Attribute 798 | { 799 | public HtmlElementAttributesAttribute() { } 800 | 801 | public HtmlElementAttributesAttribute([NotNull] string name) 802 | { 803 | Name = name; 804 | } 805 | 806 | [CanBeNull] public string Name { get; private set; } 807 | } 808 | 809 | [AttributeUsage(AttributeTargets.Parameter | AttributeTargets.Field | AttributeTargets.Property)] 810 | public sealed class HtmlAttributeValueAttribute : Attribute 811 | { 812 | public HtmlAttributeValueAttribute([NotNull] string name) 813 | { 814 | Name = name; 815 | } 816 | 817 | [NotNull] public string Name { get; private set; } 818 | } 819 | 820 | /// 821 | /// Razor attribute. Indicates that a parameter or a method is a Razor section. 822 | /// Use this attribute for custom wrappers similar to 823 | /// System.Web.WebPages.WebPageBase.RenderSection(String). 824 | /// 825 | [AttributeUsage(AttributeTargets.Parameter | AttributeTargets.Method)] 826 | public sealed class RazorSectionAttribute : Attribute { } 827 | 828 | /// 829 | /// Indicates how method, constructor invocation or property access 830 | /// over collection type affects content of the collection. 831 | /// 832 | [AttributeUsage(AttributeTargets.Method | AttributeTargets.Constructor | AttributeTargets.Property)] 833 | public sealed class CollectionAccessAttribute : Attribute 834 | { 835 | public CollectionAccessAttribute(CollectionAccessType collectionAccessType) 836 | { 837 | CollectionAccessType = collectionAccessType; 838 | } 839 | 840 | public CollectionAccessType CollectionAccessType { get; private set; } 841 | } 842 | 843 | [Flags] 844 | public enum CollectionAccessType 845 | { 846 | /// Method does not use or modify content of the collection. 847 | None = 0, 848 | /// Method only reads content of the collection but does not modify it. 849 | Read = 1, 850 | /// Method can change content of the collection but does not add new elements. 851 | ModifyExistingContent = 2, 852 | /// Method can add new elements to the collection. 853 | UpdatedContent = ModifyExistingContent | 4 854 | } 855 | 856 | /// 857 | /// Indicates that the marked method is assertion method, i.e. it halts control flow if 858 | /// one of the conditions is satisfied. To set the condition, mark one of the parameters with 859 | /// attribute. 860 | /// 861 | [AttributeUsage(AttributeTargets.Method)] 862 | public sealed class AssertionMethodAttribute : Attribute { } 863 | 864 | /// 865 | /// Indicates the condition parameter of the assertion method. The method itself should be 866 | /// marked by attribute. The mandatory argument of 867 | /// the attribute is the assertion type. 868 | /// 869 | [AttributeUsage(AttributeTargets.Parameter)] 870 | public sealed class AssertionConditionAttribute : Attribute 871 | { 872 | public AssertionConditionAttribute(AssertionConditionType conditionType) 873 | { 874 | ConditionType = conditionType; 875 | } 876 | 877 | public AssertionConditionType ConditionType { get; private set; } 878 | } 879 | 880 | /// 881 | /// Specifies assertion type. If the assertion method argument satisfies the condition, 882 | /// then the execution continues. Otherwise, execution is assumed to be halted. 883 | /// 884 | public enum AssertionConditionType 885 | { 886 | /// Marked parameter should be evaluated to true. 887 | IS_TRUE = 0, 888 | /// Marked parameter should be evaluated to false. 889 | IS_FALSE = 1, 890 | /// Marked parameter should be evaluated to null value. 891 | IS_NULL = 2, 892 | /// Marked parameter should be evaluated to not null value. 893 | IS_NOT_NULL = 3, 894 | } 895 | 896 | /// 897 | /// Indicates that the marked method unconditionally terminates control flow execution. 898 | /// For example, it could unconditionally throw exception. 899 | /// 900 | [Obsolete("Use [ContractAnnotation('=> halt')] instead")] 901 | [AttributeUsage(AttributeTargets.Method)] 902 | public sealed class TerminatesProgramAttribute : Attribute { } 903 | 904 | /// 905 | /// Indicates that method is pure LINQ method, with postponed enumeration (like Enumerable.Select, 906 | /// .Where). This annotation allows inference of [InstantHandle] annotation for parameters 907 | /// of delegate type by analyzing LINQ method chains. 908 | /// 909 | [AttributeUsage(AttributeTargets.Method)] 910 | public sealed class LinqTunnelAttribute : Attribute { } 911 | 912 | /// 913 | /// Indicates that IEnumerable, passed as parameter, is not enumerated. 914 | /// 915 | [AttributeUsage(AttributeTargets.Parameter)] 916 | public sealed class NoEnumerationAttribute : Attribute { } 917 | 918 | /// 919 | /// Indicates that parameter is regular expression pattern. 920 | /// 921 | [AttributeUsage(AttributeTargets.Parameter)] 922 | public sealed class RegexPatternAttribute : Attribute { } 923 | 924 | /// 925 | /// Prevents the Member Reordering feature from tossing members of the marked class. 926 | /// 927 | /// 928 | /// The attribute must be mentioned in your member reordering patterns 929 | /// 930 | [AttributeUsage( 931 | AttributeTargets.Class | AttributeTargets.Interface | AttributeTargets.Struct | AttributeTargets.Enum)] 932 | public sealed class NoReorderAttribute : Attribute { } 933 | 934 | /// 935 | /// XAML attribute. Indicates the type that has ItemsSource property and should be treated 936 | /// as ItemsControl-derived type, to enable inner items DataContext type resolve. 937 | /// 938 | [AttributeUsage(AttributeTargets.Class)] 939 | public sealed class XamlItemsControlAttribute : Attribute { } 940 | 941 | /// 942 | /// XAML attribute. Indicates the property of some BindingBase-derived type, that 943 | /// is used to bind some item of ItemsControl-derived type. This annotation will 944 | /// enable the DataContext type resolve for XAML bindings for such properties. 945 | /// 946 | /// 947 | /// Property should have the tree ancestor of the ItemsControl type or 948 | /// marked with the attribute. 949 | /// 950 | [AttributeUsage(AttributeTargets.Property)] 951 | public sealed class XamlItemBindingOfItemsControlAttribute : Attribute { } 952 | 953 | [AttributeUsage(AttributeTargets.Class, AllowMultiple = true)] 954 | public sealed class AspChildControlTypeAttribute : Attribute 955 | { 956 | public AspChildControlTypeAttribute([NotNull] string tagName, [NotNull] Type controlType) 957 | { 958 | TagName = tagName; 959 | ControlType = controlType; 960 | } 961 | 962 | [NotNull] public string TagName { get; private set; } 963 | 964 | [NotNull] public Type ControlType { get; private set; } 965 | } 966 | 967 | [AttributeUsage(AttributeTargets.Property | AttributeTargets.Method)] 968 | public sealed class AspDataFieldAttribute : Attribute { } 969 | 970 | [AttributeUsage(AttributeTargets.Property | AttributeTargets.Method)] 971 | public sealed class AspDataFieldsAttribute : Attribute { } 972 | 973 | [AttributeUsage(AttributeTargets.Property)] 974 | public sealed class AspMethodPropertyAttribute : Attribute { } 975 | 976 | [AttributeUsage(AttributeTargets.Class, AllowMultiple = true)] 977 | public sealed class AspRequiredAttributeAttribute : Attribute 978 | { 979 | public AspRequiredAttributeAttribute([NotNull] string attribute) 980 | { 981 | Attribute = attribute; 982 | } 983 | 984 | [NotNull] public string Attribute { get; private set; } 985 | } 986 | 987 | [AttributeUsage(AttributeTargets.Property)] 988 | public sealed class AspTypePropertyAttribute : Attribute 989 | { 990 | public bool CreateConstructorReferences { get; private set; } 991 | 992 | public AspTypePropertyAttribute(bool createConstructorReferences) 993 | { 994 | CreateConstructorReferences = createConstructorReferences; 995 | } 996 | } 997 | 998 | [AttributeUsage(AttributeTargets.Assembly, AllowMultiple = true)] 999 | public sealed class RazorImportNamespaceAttribute : Attribute 1000 | { 1001 | public RazorImportNamespaceAttribute([NotNull] string name) 1002 | { 1003 | Name = name; 1004 | } 1005 | 1006 | [NotNull] public string Name { get; private set; } 1007 | } 1008 | 1009 | [AttributeUsage(AttributeTargets.Assembly, AllowMultiple = true)] 1010 | public sealed class RazorInjectionAttribute : Attribute 1011 | { 1012 | public RazorInjectionAttribute([NotNull] string type, [NotNull] string fieldName) 1013 | { 1014 | Type = type; 1015 | FieldName = fieldName; 1016 | } 1017 | 1018 | [NotNull] public string Type { get; private set; } 1019 | 1020 | [NotNull] public string FieldName { get; private set; } 1021 | } 1022 | 1023 | [AttributeUsage(AttributeTargets.Assembly, AllowMultiple = true)] 1024 | public sealed class RazorDirectiveAttribute : Attribute 1025 | { 1026 | public RazorDirectiveAttribute([NotNull] string directive) 1027 | { 1028 | Directive = directive; 1029 | } 1030 | 1031 | [NotNull] public string Directive { get; private set; } 1032 | } 1033 | 1034 | [AttributeUsage(AttributeTargets.Method)] 1035 | public sealed class RazorHelperCommonAttribute : Attribute { } 1036 | 1037 | [AttributeUsage(AttributeTargets.Property)] 1038 | public sealed class RazorLayoutAttribute : Attribute { } 1039 | 1040 | [AttributeUsage(AttributeTargets.Method)] 1041 | public sealed class RazorWriteLiteralMethodAttribute : Attribute { } 1042 | 1043 | [AttributeUsage(AttributeTargets.Method)] 1044 | public sealed class RazorWriteMethodAttribute : Attribute { } 1045 | 1046 | [AttributeUsage(AttributeTargets.Parameter)] 1047 | public sealed class RazorWriteMethodParameterAttribute : Attribute { } 1048 | } -------------------------------------------------------------------------------- /WithGenericInternal/Model.cs: -------------------------------------------------------------------------------- 1 | using System.ComponentModel; 2 | using System.Runtime.CompilerServices; 3 | using Validar; 4 | 5 | namespace WithGenericInternal 6 | { 7 | [InjectValidation] 8 | public class Model : INotifyPropertyChanged 9 | { 10 | string property2; 11 | string property1; 12 | 13 | public string Property1 { 14 | get => property1; set 15 | { 16 | property1 = value; 17 | OnPropertyChanged(); 18 | } 19 | } 20 | 21 | public string Property2 { 22 | get => property2; set 23 | { 24 | property2 = value; 25 | OnPropertyChanged(); 26 | } 27 | } 28 | 29 | public event PropertyChangedEventHandler PropertyChanged; 30 | 31 | void OnPropertyChanged([CallerMemberName] string propertyName = null) 32 | { 33 | PropertyChanged?.Invoke(this, new PropertyChangedEventArgs(propertyName)); 34 | } 35 | } 36 | } -------------------------------------------------------------------------------- /WithGenericInternal/ModelValidator.cs: -------------------------------------------------------------------------------- 1 | using FluentValidation; 2 | 3 | namespace WithGenericInternal 4 | { 5 | public class ModelValidator : AbstractValidator 6 | { 7 | public ModelValidator() 8 | { 9 | RuleFor(x => x.Property1) 10 | .NotEmpty() 11 | .WithMessage("'Property1' message."); 12 | RuleFor(x => x.Property2) 13 | .NotEmpty() 14 | .WithMessage("'Property2' message."); 15 | } 16 | } 17 | } -------------------------------------------------------------------------------- /WithGenericInternal/ModelWithImplementation.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | using System.Collections; 3 | using System.ComponentModel; 4 | using System.Runtime.CompilerServices; 5 | using Validar; 6 | 7 | namespace WithGenericInternal 8 | { 9 | [InjectValidation] 10 | public class ModelWithImplementation : 11 | INotifyPropertyChanged, 12 | IDataErrorInfo, 13 | INotifyDataErrorInfo 14 | { 15 | ValidationTemplate validationTemplate; 16 | public ModelWithImplementation() 17 | { 18 | validationTemplate = new ValidationTemplate(this); 19 | } 20 | 21 | public string this[string columnName] => validationTemplate[columnName]; 22 | 23 | public IEnumerable GetErrors(string propertyName) 24 | { 25 | return validationTemplate.GetErrors(propertyName); 26 | } 27 | public bool HasErrors => validationTemplate.HasErrors; 28 | 29 | public event EventHandler ErrorsChanged 30 | { 31 | add => validationTemplate.ErrorsChanged += value; 32 | remove => validationTemplate.ErrorsChanged -= value; 33 | } 34 | 35 | public string Error => validationTemplate.Error; 36 | 37 | string property1; 38 | 39 | public string Property1 40 | { 41 | get => property1; 42 | set 43 | { 44 | if (value != property1) 45 | { 46 | property1 = value; 47 | OnPropertyChanged(); 48 | } 49 | } 50 | } 51 | 52 | string property2; 53 | 54 | public string Property2 55 | { 56 | get => property2; 57 | set 58 | { 59 | if (value != property2) 60 | { 61 | property2 = value; 62 | OnPropertyChanged(); 63 | } 64 | } 65 | } 66 | 67 | void OnPropertyChanged([CallerMemberName] string propertyName = null) 68 | { 69 | PropertyChanged?.Invoke(this, new PropertyChangedEventArgs(propertyName)); 70 | } 71 | 72 | public event PropertyChangedEventHandler PropertyChanged; 73 | } 74 | } -------------------------------------------------------------------------------- /WithGenericInternal/ModelWithImplementationValidator.cs: -------------------------------------------------------------------------------- 1 | using FluentValidation; 2 | 3 | namespace WithGenericInternal 4 | { 5 | public class ModelWithImplementationValidator : 6 | AbstractValidator 7 | { 8 | public ModelWithImplementationValidator() 9 | { 10 | RuleFor(x => x.Property1) 11 | .NotEmpty() 12 | .WithMessage("'Property1' message."); 13 | RuleFor(x => x.Property2) 14 | .NotEmpty() 15 | .WithMessage("'Property2' message."); 16 | } 17 | } 18 | } -------------------------------------------------------------------------------- /WithGenericInternal/ValidationFactory.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | using System.Collections.Concurrent; 3 | using System.ComponentModel; 4 | using FluentValidation; 5 | 6 | namespace WithGenericInternal 7 | { 8 | public static class ValidationFactory 9 | { 10 | static ConcurrentDictionary validators = new ConcurrentDictionary(); 11 | 12 | public static IValidator GetValidator() 13 | where T : INotifyPropertyChanged 14 | { var modelType = typeof (T); 15 | var modelTypeHandle = modelType.TypeHandle; 16 | if (!validators.TryGetValue(modelTypeHandle, out var validator)) 17 | { 18 | var typeName = $"{modelType.Namespace}.{modelType.Name}Validator"; 19 | var type = modelType.Assembly.GetType(typeName, true); 20 | validators[modelTypeHandle] = validator = (IValidator) Activator.CreateInstance(type); 21 | } 22 | 23 | return (IValidator) validator; 24 | } 25 | } 26 | } -------------------------------------------------------------------------------- /WithGenericInternal/ValidationTemplate.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | using System.Collections; 3 | using System.ComponentModel; 4 | using System.Linq; 5 | using FluentValidation; 6 | using FluentValidation.Results; 7 | 8 | namespace WithGenericInternal 9 | { 10 | public class ValidationTemplate : 11 | IDataErrorInfo, 12 | INotifyDataErrorInfo 13 | where T : INotifyPropertyChanged 14 | { 15 | IValidator validator; 16 | ValidationResult validationResult; 17 | ValidationContext context; 18 | 19 | public ValidationTemplate(T target) 20 | { 21 | validator = ValidationFactory.GetValidator(); 22 | context = new ValidationContext(target); 23 | validationResult = validator.Validate(context); 24 | target.PropertyChanged += Validate; 25 | } 26 | 27 | void Validate(object sender, PropertyChangedEventArgs e) 28 | { 29 | validationResult = validator.Validate(context); 30 | foreach (var error in validationResult.Errors) 31 | { 32 | RaiseErrorsChanged(error.PropertyName); 33 | } 34 | } 35 | 36 | public IEnumerable GetErrors(string propertyName) 37 | { 38 | return validationResult 39 | .Errors 40 | .Where(x => x.PropertyName == propertyName) 41 | .Select(x => x.ErrorMessage); 42 | } 43 | 44 | public bool HasErrors => validationResult.Errors.Count > 0; 45 | 46 | public string Error 47 | { 48 | get 49 | { 50 | var strings = validationResult 51 | .Errors 52 | .Select(x => x.ErrorMessage); 53 | return string.Join(Environment.NewLine, strings); 54 | } 55 | } 56 | 57 | public string this[string propertyName] 58 | { 59 | get 60 | { 61 | var strings = validationResult 62 | .Errors 63 | .Where(x => x.PropertyName == propertyName) 64 | .Select(x => x.ErrorMessage); 65 | return string.Join(Environment.NewLine, strings); 66 | } 67 | } 68 | 69 | public event EventHandler ErrorsChanged; 70 | 71 | void RaiseErrorsChanged(string propertyName) 72 | { 73 | var handler = ErrorsChanged; 74 | handler?.Invoke(this, new DataErrorsChangedEventArgs(propertyName)); 75 | } 76 | } 77 | } -------------------------------------------------------------------------------- /WithGenericInternal/WithGenericInternal.csproj: -------------------------------------------------------------------------------- 1 |  2 | 3 | 4 | net472;netcoreapp2.0 5 | true 6 | 7 | 8 | 9 | 10 | 11 | -------------------------------------------------------------------------------- /appveyor.yml: -------------------------------------------------------------------------------- 1 | image: Visual Studio 2022 2 | build_script: 3 | - cmd: dotnet build --configuration Release 4 | test: 5 | assemblies: 6 | - '**\*Tests.dll' 7 | artifacts: 8 | - path: nugets\*.nupkg -------------------------------------------------------------------------------- /license.txt: -------------------------------------------------------------------------------- 1 | The MIT License 2 | 3 | Copyright (c) 2012 Simon Cropp and contributors 4 | 5 | Permission is hereby granted, free of charge, to any person obtaining a copy 6 | of this software and associated documentation files (the "Software"), to deal 7 | in the Software without restriction, including without limitation the rights 8 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 9 | copies of the Software, and to permit persons to whom the Software is 10 | furnished to do so, subject to the following conditions: 11 | 12 | The above copyright notice and this permission notice shall be included in 13 | all copies or substantial portions of the Software. 14 | 15 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 16 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 17 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 18 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 19 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 20 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN 21 | THE SOFTWARE. -------------------------------------------------------------------------------- /package_icon.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/fodyarchived/Validar/1fc56edd9fb3d238873693a79852b0b296d29044/package_icon.png -------------------------------------------------------------------------------- /readme.md: -------------------------------------------------------------------------------- 1 | # Validar.Fody 2 | 3 | [![Chat on Gitter](https://img.shields.io/gitter/room/fody/fody.svg)](https://gitter.im/Fody/Fody) 4 | [![NuGet Status](https://img.shields.io/nuget/v/Validar.Fody.svg)](https://www.nuget.org/packages/Validar.Fody/) 5 | 6 | Provides validation for XAML binding models. 7 | 8 | Injects [IDataErrorInfo](http://msdn.microsoft.com/en-us/library/system.componentmodel.IDataErrorInfo.aspx) or [INotifyDataErrorInfo](http://msdn.microsoft.com/en-us/library/system.componentmodel.INotifyDataErrorInfo.aspx) code into a class at compile time. 9 | 10 | 11 | ### This is an add-in for [Fody](https://github.com/Fody/Home/) 12 | 13 | **It is expected that all developers using Fody [become a Patron on OpenCollective](https://opencollective.com/fody/contribute/patron-3059). [See Licensing/Patron FAQ](https://github.com/Fody/Home/blob/master/pages/licensing-patron-faq.md) for more information.** 14 | 15 | 16 | ## Usage 17 | 18 | See also [Fody usage](https://github.com/Fody/Home/blob/master/pages/usage.md). 19 | 20 | 21 | ### NuGet installation 22 | 23 | Install the [Validar.Fody NuGet package](https://nuget.org/packages/Validar.Fody/) and update the [Fody NuGet package](https://nuget.org/packages/Fody/): 24 | 25 | ```powershell 26 | PM> Install-Package Fody 27 | PM> Install-Package Validar.Fody 28 | ``` 29 | 30 | The `Install-Package Fody` is required since NuGet always defaults to the oldest, and most buggy, version of any dependency. 31 | 32 | 33 | ### Add to FodyWeavers.xml 34 | 35 | Add `` to [FodyWeavers.xml](https://github.com/Fody/Home/blob/master/pages/usage.md#add-fodyweaversxml) 36 | 37 | ```xml 38 | 39 | 40 | 41 | ``` 42 | 43 | 44 | ## Model Code 45 | 46 | * Must implement `INotifyPropertyChanged` (in this case implementation excluded for brevity). 47 | * Contain a `[InjectValidation]` attribute. 48 | 49 | For example 50 | 51 | ```c# 52 | [InjectValidation] 53 | public class Person : INotifyPropertyChanged 54 | { 55 | public string GivenNames { get; set; } 56 | public string FamilyName { get; set; } 57 | } 58 | ``` 59 | 60 | 61 | ### Validation template code 62 | 63 | ```c# 64 | public class ValidationTemplate : IDataErrorInfo, INotifyDataErrorInfo 65 | { 66 | public ValidationTemplate(INotifyPropertyChanged target) 67 | { 68 | // Provide an implementation 69 | } 70 | 71 | // implementation of IDataErrorInfo 72 | // implementation of INotifyDataErrorInfo 73 | } 74 | ``` 75 | 76 | 77 | ### What gets compiled 78 | 79 | Note that an instance of `ValidationTemplate` has been injected into `Person` 80 | 81 | ```c# 82 | public class Person : INotifyPropertyChanged, IDataErrorInfo, INotifyDataErrorInfo 83 | { 84 | IDataErrorInfo validationTemplate; 85 | public string GivenNames { get; set; } 86 | public string FamilyName { get; set; } 87 | 88 | public Person() 89 | { 90 | validationTemplate = new ValidationTemplate(this); 91 | } 92 | 93 | // implementation of IDataErrorInfo 94 | // implementation of INotifyDataErrorInfo 95 | } 96 | ``` 97 | 98 | 99 | ## Validation Template 100 | 101 | * Must be named `ValidationTemplate`. 102 | * Namespace doesn't matter. 103 | * Must implement either `IDataErrorInfo` or `INotifyDataErrorInfo` or both. 104 | * Have a instance constructor that takes a `INotifyPropertyChanged`. 105 | * Can be generic e.g. `ValidationTemplate where T: INotifyPropertyChanged` 106 | 107 | 108 | ### Current Assembly 109 | 110 | If `ValidationTemplate` exist in the current assembly they will be picked up automatically. 111 | 112 | 113 | ### Other Assembly 114 | 115 | If `ValidationTemplate` exist in a different assembly add a `[ValidationTemplateAttribute]` to tell Validar where to look. 116 | 117 | ```c# 118 | [assembly: ValidationTemplateAttribute(typeof(MyUtilsLibrary.ValidationTemplate))] 119 | ``` 120 | 121 | 122 | ## Validation Template Implementations 123 | 124 | Custom `ValidationTemplate` implementations are supported. Here are some suggested implementations that enable common validation libraries. 125 | 126 | 127 | ### [FluentValidation](https://github.com/JeremySkinner/FluentValidation) 128 | 129 | ``` 130 | Install-Package FluentValidation 131 | ``` 132 | 133 | Note that FluentValidation extracts the model validation into a different class 134 | 135 | ```c# 136 | public class PersonValidator : AbstractValidator 137 | { 138 | public PersonValidator() 139 | { 140 | RuleFor(x => x.FamilyName).NotEmpty(); 141 | RuleFor(x => x.GivenNames).NotEmpty(); 142 | } 143 | } 144 | 145 | public class ValidationTemplate : IDataErrorInfo, INotifyDataErrorInfo where T : INotifyPropertyChanged 146 | { 147 | T target; 148 | IValidator validator; 149 | ValidationResult validationResult; 150 | ValidationContext context; 151 | static ConcurrentDictionary validators = new ConcurrentDictionary(); 152 | 153 | public ValidationTemplate(T target) 154 | { 155 | this.target = target; 156 | validator = GetValidator(target.GetType()); 157 | context = new ValidationContext(target); 158 | validationResult = validator.Validate(context); 159 | target.PropertyChanged += Validate; 160 | } 161 | 162 | static IValidator GetValidator(Type modelType) 163 | { 164 | if (!validators.TryGetValue(modelType.TypeHandle, out var validator)) 165 | { 166 | var typeName = string.Format("{0}.{1}Validator", modelType.Namespace, modelType.Name); 167 | var type = modelType.Assembly.GetType(typeName, true); 168 | validators[modelType.TypeHandle] = validator = (IValidator)Activator.CreateInstance(type); 169 | } 170 | return validator; 171 | } 172 | 173 | void Validate(object sender, PropertyChangedEventArgs e) 174 | { 175 | validationResult = validator.Validate(context); 176 | foreach (var error in validationResult.Errors) 177 | { 178 | RaiseErrorsChanged(error.PropertyName); 179 | } 180 | } 181 | 182 | public IEnumerable GetErrors(string propertyName) 183 | { 184 | return validationResult.Errors 185 | .Where(x => x.PropertyName == propertyName) 186 | .Select(x => x.ErrorMessage); 187 | } 188 | 189 | public bool HasErrors 190 | { 191 | get { return validationResult.Errors.Count > 0; } 192 | } 193 | 194 | public string Error 195 | { 196 | get 197 | { 198 | var strings = validationResult.Errors.Select(x => x.ErrorMessage) 199 | .ToArray(); 200 | return string.Join(Environment.NewLine, strings); 201 | } 202 | } 203 | 204 | public string this[string propertyName] 205 | { 206 | get 207 | { 208 | var strings = validationResult.Errors.Where(x => x.PropertyName == propertyName) 209 | .Select(x => x.ErrorMessage) 210 | .ToArray(); 211 | return string.Join(Environment.NewLine, strings); 212 | } 213 | } 214 | 215 | public event EventHandler ErrorsChanged; 216 | 217 | void RaiseErrorsChanged(string propertyName) 218 | { 219 | var handler = ErrorsChanged; 220 | if (handler != null) 221 | { 222 | handler(this, new DataErrorsChangedEventArgs(propertyName)); 223 | } 224 | } 225 | } 226 | ``` 227 | 228 | 229 | ### [Sandra.SimpleValidator](https://github.com/phillip-haydon/Sandra.SimpleValidator) 230 | 231 | ```c# 232 | Install-Package Sandra.SimpleValidator 233 | ``` 234 | 235 | Note that Sandra.SimpleValidator extracts the model validation into a different class 236 | 237 | ```c# 238 | public class PersonValidator : ValidateThis 239 | { 240 | public PersonValidator() 241 | { 242 | For(x => x.GivenNames) 243 | .Ensure(new Required().WithMessage("'Given Names' should not be empty.")); 244 | 245 | For(x => x.FamilyName) 246 | .Ensure(new Required().WithMessage("'Family Name' should not be empty.")); 247 | } 248 | } 249 | 250 | public class ValidationTemplate : IDataErrorInfo, INotifyDataErrorInfo 251 | { 252 | INotifyPropertyChanged target; 253 | IModelValidator validator; 254 | ValidationResult validationResult; 255 | 256 | public ValidationTemplate(INotifyPropertyChanged target) 257 | { 258 | this.target = target; 259 | validator = GetValidator(target.GetType()); 260 | validationResult = validator.Validate(target); 261 | target.PropertyChanged += Validate; 262 | } 263 | 264 | static ConcurrentDictionary validators = new ConcurrentDictionary(); 265 | 266 | static IModelValidator GetValidator(Type modelType) 267 | { 268 | if (!validators.TryGetValue(modelType.TypeHandle, out var validator)) 269 | { 270 | var typeName = string.Format("{0}.{1}Validator", modelType.Namespace, modelType.Name); 271 | var type = modelType.Assembly.GetType(typeName, true); 272 | validators[modelType.TypeHandle] = validator = (IModelValidator)Activator.CreateInstance(type); 273 | } 274 | return validator; 275 | } 276 | 277 | void Validate(object sender, PropertyChangedEventArgs e) 278 | { 279 | validationResult = validator.Validate(target); 280 | foreach (var error in validationResult.Messages) 281 | { 282 | RaiseErrorsChanged(error.PropertyName); 283 | } 284 | } 285 | 286 | public IEnumerable GetErrors(string propertyName) 287 | { 288 | return validationResult.Messages 289 | .Where(x => x.PropertyName == propertyName) 290 | .Select(x => x.Message); 291 | } 292 | 293 | public bool HasErrors 294 | { 295 | get { return validationResult.IsInvalid; } 296 | } 297 | 298 | public string Error 299 | { 300 | get 301 | { 302 | var strings = validationResult.Messages.Select(x => x.Message) 303 | .ToArray(); 304 | return string.Join(Environment.NewLine, strings); 305 | } 306 | } 307 | 308 | public string this[string propertyName] 309 | { 310 | get 311 | { 312 | var strings = validationResult.Messages.Where(x => x.PropertyName == propertyName) 313 | .Select(x => x.Message) 314 | .ToArray(); 315 | return string.Join(Environment.NewLine, strings); 316 | } 317 | } 318 | 319 | public event EventHandler ErrorsChanged; 320 | 321 | void RaiseErrorsChanged(string propertyName) 322 | { 323 | var handler = ErrorsChanged; 324 | if (handler != null) 325 | { 326 | handler(this, new DataErrorsChangedEventArgs(propertyName)); 327 | } 328 | } 329 | } 330 | ``` 331 | 332 | 333 | ### [DataAnnotations](http://msdn.microsoft.com/en-us/library/system.componentmodel.dataannotations.aspx) 334 | 335 | ```c# 336 | public class ValidationTemplate : 337 | IDataErrorInfo, 338 | INotifyDataErrorInfo 339 | { 340 | INotifyPropertyChanged target; 341 | ValidationContext validationContext; 342 | List validationResults; 343 | 344 | public ValidationTemplate(INotifyPropertyChanged target) 345 | { 346 | this.target = target; 347 | validationContext = new ValidationContext(target, null, null); 348 | validationResults = new List(); 349 | Validator.TryValidateObject(target, validationContext, validationResults, true); 350 | target.PropertyChanged += Validate; 351 | } 352 | 353 | void Validate(object sender, PropertyChangedEventArgs e) 354 | { 355 | validationResults.Clear(); 356 | Validator.TryValidateObject(target, validationContext, validationResults, true); 357 | var hashSet = new HashSet(validationResults.SelectMany(x => x.MemberNames)); 358 | foreach (var error in hashSet) 359 | { 360 | RaiseErrorsChanged(error); 361 | } 362 | } 363 | 364 | public IEnumerable GetErrors(string propertyName) 365 | { 366 | return validationResults.Where(x => x.MemberNames.Contains(propertyName)) 367 | .Select(x => x.ErrorMessage); 368 | } 369 | 370 | public bool HasErrors 371 | { 372 | get { return validationResults.Count > 0; } 373 | } 374 | 375 | public string Error 376 | { 377 | get 378 | { 379 | var strings = validationResults.Select(x => x.ErrorMessage) 380 | .ToArray(); 381 | return string.Join(Environment.NewLine, strings); 382 | } 383 | } 384 | 385 | public string this[string propertyName] 386 | { 387 | get 388 | { 389 | var strings = validationResults.Where(x => x.MemberNames.Contains(propertyName)) 390 | .Select(x => x.ErrorMessage) 391 | .ToArray(); 392 | return string.Join(Environment.NewLine, strings); 393 | } 394 | } 395 | 396 | public event EventHandler ErrorsChanged; 397 | 398 | void RaiseErrorsChanged(string propertyName) 399 | { 400 | var handler = ErrorsChanged; 401 | if (handler != null) 402 | { 403 | handler(this, new DataErrorsChangedEventArgs(propertyName)); 404 | } 405 | } 406 | } 407 | ``` 408 | 409 | 410 | ## Icon 411 | 412 | [Check Mark](https://thenounproject.com/noun/check-mark/#icon-No6407) designed by [Mateo Zlatar](https://thenounproject.com/mateozlatar) from [The Noun Project](https://thenounproject.com). --------------------------------------------------------------------------------