CSLA.NET 5.4.2
CSLA .NET is a software development framework that helps you build a reusable, maintainable object-oriented business layer for your app.
CslaModelBinder.cs
Go to the documentation of this file.
1//-----------------------------------------------------------------------
2// <copyright file="CslaModelBinder.cs" company="Marimer LLC">
3// Copyright (c) Marimer LLC. All rights reserved.
4// Website: https://cslanet.com
5// </copyright>
6// <summary>Model binder for use with CSLA .NET editable business objects.</summary>
7//-----------------------------------------------------------------------
8#if NETSTANDARD2_0 || NET5_0 || NETCORE3_1
9using System;
10using System.Collections;
11using System.Collections.Generic;
12using System.Linq;
13using System.Threading.Tasks;
14using Microsoft.AspNetCore.Mvc.ModelBinding;
15
16namespace Csla.Web.Mvc
17{
22 public class CslaModelBinder : Server.ObjectFactory, IModelBinder
23 {
28 public CslaModelBinder(Func<Type, Task<object>> instanceCreator)
29 {
30 _instanceCreator = instanceCreator;
31 }
32
38 public CslaModelBinder(Func<Type, Task<object>> instanceCreator, Func<IList, Type, Dictionary<string, string>, object> childCreator)
39 {
40 _instanceCreator = instanceCreator;
41 _childCreator = childCreator;
42 }
43
44 private readonly Func<Type, Task<object>> _instanceCreator;
45 private readonly Func<IList, Type, Dictionary<string, string>, object> _childCreator;
46
51 public async Task BindModelAsync(ModelBindingContext bindingContext)
52 {
53 if (bindingContext == null)
54 {
55 throw new ArgumentNullException(nameof(bindingContext));
56 }
57
58 var result = await _instanceCreator(bindingContext.ModelType);
59 if (result == null)
60 return;
61
62 if (typeof(Core.IEditableCollection).IsAssignableFrom(bindingContext.ModelType))
63 {
64 BindBusinessListBase(bindingContext, result);
65 }
66 else if (typeof(Core.IEditableBusinessObject).IsAssignableFrom(bindingContext.ModelType))
67 {
68 BindBusinessBase(bindingContext, result);
69 }
70 else
71 {
72 return;
73 }
74
75 bindingContext.Result = ModelBindingResult.Success(result);
76 return;
77 }
78
79 private void BindBusinessBase(ModelBindingContext bindingContext, object result)
80 {
81 var properties = Core.FieldManager.PropertyInfoManager.GetRegisteredProperties(bindingContext.ModelType);
82 foreach (var item in properties)
83 {
84 string index;
85 if (string.IsNullOrEmpty(bindingContext.ModelName))
86 index = $"{item.Name}";
87 else
88 index = $"{bindingContext.ModelName}.{item.Name}";
89 BindSingleProperty(bindingContext, result, item, index);
90 }
91 CheckRules(result);
92 }
93
94 private void BindBusinessListBase(ModelBindingContext bindingContext, object result)
95 {
96 var formKeys = bindingContext.ActionContext.HttpContext.Request.Form.Keys.Where(_ => _.StartsWith(bindingContext.ModelName));
97 var childType = Utilities.GetChildItemType(bindingContext.ModelType);
98 var properties = Core.FieldManager.PropertyInfoManager.GetRegisteredProperties(childType);
99 var list = (IList)result;
100
101 var itemCount = formKeys.Count() / properties.Count();
102 for (int i = 0; i < itemCount; i++)
103 {
104 var child = _childCreator(
105 list, childType,
106 GetFormValuesForObject(bindingContext.ActionContext.HttpContext.Request.Form, bindingContext.ModelName, i, properties));
107 MarkAsChild(child);
108 if (child == null)
109 throw new InvalidOperationException($"Could not create instance of child type {childType}");
110 foreach (var item in properties)
111 {
112 var index = $"{bindingContext.ModelName}[{i}].{item.Name}";
113 BindSingleProperty(bindingContext, child, item, index);
114 }
115 CheckRules(child);
116 if (!list.Contains(child))
117 list.Add(child);
118 }
119 }
120
121 private Dictionary<string, string> GetFormValuesForObject(
122 Microsoft.AspNetCore.Http.IFormCollection formData,
123 string modelName,
124 int index,
125 Core.FieldManager.PropertyInfoList properties)
126 {
127 var result = new Dictionary<string, string>();
128 foreach (var item in properties)
129 {
130 var key = $"{modelName}[{index}].{item.Name}";
131 result.Add(item.Name, formData[key]);
132 }
133 return result;
134 }
135
136 private void BindSingleProperty(ModelBindingContext bindingContext, object result, Core.IPropertyInfo item, string index)
137 {
138 try
139 {
140 var value = bindingContext.ActionContext.HttpContext.Request.Form[index].FirstOrDefault();
141 try
142 {
143 if (item.Type.Equals(typeof(string)))
144 Reflection.MethodCaller.CallPropertySetter(result, item.Name, value);
145 else if (value != null)
146 Reflection.MethodCaller.CallPropertySetter(result, item.Name, Utilities.CoerceValue(item.Type, value.GetType(), null, value));
147 else
148 Reflection.MethodCaller.CallPropertySetter(result, item.Name, null);
149 }
150 catch
151 {
152 if (item.Type.Equals(typeof(string)))
153 LoadProperty(result, item, value);
154 else if (value != null)
155 LoadProperty(result, item, Utilities.CoerceValue(item.Type, value.GetType(), null, value));
156 else
157 LoadProperty(result, item, null);
158 }
159 }
160 catch (Exception ex)
161 {
162 throw new Exception($"Could not map {index} to model", ex);
163 }
164 }
165 }
166
171 public class CslaModelBinderProvider : IModelBinderProvider
172 {
177 public CslaModelBinderProvider()
178 : this(CreateInstance, CreateChild)
179 { }
180
187 public CslaModelBinderProvider(Func<Type, Task<object>> instanceCreator, Func<IList, Type, Dictionary<string, string>, object> childCreator)
188 {
189 _instanceCreator = instanceCreator;
190 _childCreator = childCreator;
191 }
192
193 internal static Task<object> CreateInstance(Type type)
194 {
195 var tcs = new TaskCompletionSource<object>();
196 tcs.SetResult(Reflection.MethodCaller.CreateInstance(type));
197 return tcs.Task;
198 }
199
200 internal static object CreateChild(IList parent, Type type, Dictionary<string, string> values)
201 {
202 return Reflection.MethodCaller.CreateInstance(type);
203 }
204
205 private readonly Func<Type, Task<object>> _instanceCreator;
206 private readonly Func<IList, Type, Dictionary<string, string>, object> _childCreator;
207
212 public IModelBinder GetBinder(ModelBinderProviderContext context)
213 {
214 if (typeof(Core.IEditableCollection).IsAssignableFrom(context.Metadata.ModelType))
215 return new CslaModelBinder(_instanceCreator, _childCreator);
216 if (typeof(IBusinessBase).IsAssignableFrom(context.Metadata.ModelType))
217 return new CslaModelBinder(_instanceCreator);
218 return null;
219 }
220 }
221}
222#elif !NETSTANDARD
223using System;
224using System.Collections.Generic;
225using System.Linq;
226using System.Web.Mvc;
227using System.ComponentModel;
228using System.Collections;
229
230namespace Csla.Web.Mvc
231{
236 public class CslaModelBinder : DefaultModelBinder
237 {
238 private bool _checkRulesOnModelUpdated;
239
244 public CslaModelBinder(bool CheckRulesOnModelUpdated = true)
245 {
246 _checkRulesOnModelUpdated = CheckRulesOnModelUpdated;
247 }
248
255 public override object BindModel(ControllerContext controllerContext, ModelBindingContext bindingContext)
256 {
257 if (typeof(Core.IEditableCollection).IsAssignableFrom((bindingContext.ModelType)))
258 return BindCslaCollection(controllerContext, bindingContext);
259
260 var suppress = bindingContext.Model as Core.ICheckRules;
261 if (suppress != null)
262 suppress.SuppressRuleChecking();
263 var result = base.BindModel(controllerContext, bindingContext);
264 return result;
265 }
266
273 private object BindCslaCollection(ControllerContext controllerContext, ModelBindingContext bindingContext)
274 {
275 if (bindingContext.Model == null)
276 bindingContext.ModelMetadata.Model = CreateModel(controllerContext, bindingContext, bindingContext.ModelType);
277
278 var collection = (IList)bindingContext.Model;
279 for (int currIdx = 0; currIdx < collection.Count; currIdx++)
280 {
281 string subIndexKey = CreateSubIndexName(bindingContext.ModelName, currIdx);
282 if (!bindingContext.ValueProvider.ContainsPrefix(subIndexKey))
283 continue; //no value to update skip
284 var elementModel = collection[currIdx];
285 var suppress = elementModel as Core.ICheckRules;
286 if (suppress != null)
287 suppress.SuppressRuleChecking();
288 var elementContext = new ModelBindingContext()
289 {
290 ModelMetadata = ModelMetadataProviders.Current.GetMetadataForType(() => elementModel, elementModel.GetType()),
291 ModelName = subIndexKey,
292 ModelState = bindingContext.ModelState,
293 PropertyFilter = bindingContext.PropertyFilter,
294 ValueProvider = bindingContext.ValueProvider
295 };
296
297 if (OnModelUpdating(controllerContext, elementContext))
298 {
299 //update element's properties
300 foreach (PropertyDescriptor property in GetFilteredModelProperties(controllerContext, elementContext))
301 {
302 BindProperty(controllerContext, elementContext, property);
303 }
304 OnModelUpdated(controllerContext, elementContext);
305 }
306 }
307
308 return bindingContext.Model;
309 }
310
318 protected override object CreateModel(ControllerContext controllerContext, ModelBindingContext bindingContext, Type modelType)
319 {
320 var controller = controllerContext.Controller as IModelCreator;
321 if (controller != null)
322 return controller.CreateModel(modelType);
323 else
324 return base.CreateModel(controllerContext, bindingContext, modelType);
325 }
326
333 protected override void OnModelUpdated(ControllerContext controllerContext, ModelBindingContext bindingContext)
334 {
335 var obj = bindingContext.Model as Core.BusinessBase;
336 if (obj != null)
337 {
338 if (this._checkRulesOnModelUpdated)
339 {
340 var suppress = obj as Core.ICheckRules;
341 if (suppress != null)
342 {
343 suppress.ResumeRuleChecking();
344 suppress.CheckRules();
345 }
346 }
347 var errors = from r in obj.BrokenRulesCollection
348 where r.Severity == Rules.RuleSeverity.Error
349 select r;
350 foreach (var item in errors)
351 {
352 ModelState state;
353 string mskey = CreateSubPropertyName(bindingContext.ModelName, item.Property ?? string.Empty);
354 if (bindingContext.ModelState.TryGetValue(mskey, out state))
355 {
356 if (state.Errors.Where(e => e.ErrorMessage == item.Description).Any())
357 continue;
358 else
359 bindingContext.ModelState.AddModelError(mskey, item.Description);
360 }
361 else if (mskey == string.Empty)
362 bindingContext.ModelState.AddModelError(bindingContext.ModelName, item.Description);
363 }
364 }
365 else
366 if (!(bindingContext.Model is IViewModel))
367 base.OnModelUpdated(controllerContext, bindingContext);
368 }
369
378 protected override void OnPropertyValidated(ControllerContext controllerContext, ModelBindingContext bindingContext, System.ComponentModel.PropertyDescriptor propertyDescriptor, object value)
379 {
380 if (!(bindingContext.Model is Core.BusinessBase))
381 base.OnPropertyValidated(controllerContext, bindingContext, propertyDescriptor, value);
382 }
383 }
384}
385#endif
Model binder for use with CSLA .NET editable business objects.
override object CreateModel(ControllerContext controllerContext, ModelBindingContext bindingContext, Type modelType)
Creates an instance of the model if the controller implements IModelCreator.
CslaModelBinder(bool CheckRulesOnModelUpdated=true)
Creates an instance of the model binder.
override object BindModel(ControllerContext controllerContext, ModelBindingContext bindingContext)
Binds the model by using the specified controller context and binding context.
override void OnModelUpdated(ControllerContext controllerContext, ModelBindingContext bindingContext)
Checks the validation rules for properties after the Model has been updated.
override void OnPropertyValidated(ControllerContext controllerContext, ModelBindingContext bindingContext, System.ComponentModel.PropertyDescriptor propertyDescriptor, object value)
Prevents IDataErrorInfo validation from operating against editable objects.
ASP.NET MVC model creator.
object CreateModel(Type modelType)
Creates a model object of the specified type.
Defines a CSLA .NET MVC viewmodel object.
@ CheckRules
Called from CheckRules