I sometime ago asked how to add some kind of localized url's, were IPageRouteModelConvention came into play in a, for me, perfect way.
With that I'm able to have routes in different languages/names.
If I use www.domain.com/nyheter (swedish) or www.domain.com/sistenytt (norwegian) I still only find, in RouteData, that the News route were used (RouteData.Values["page"]).
How do I get which version?
I know I can check/parse the context.Request.Path but am wondering if there is a built-in property that will give me it instead.
In startup
services.AddMvc()
.SetCompatibilityVersion(CompatibilityVersion.Version_2_2).AddRazorPagesOptions(options =>
{
options.Conventions.Add(new LocalizedPageRouteModelConvention(new LocalizationService(appsettings.Routes)));
});
appsettings.Routes is read from appsettings.json
"Routes": [
{
"Page": "/Pages/News.cshtml",
"Versions": [ "nyheter", "sistenytt" ]
},
and so on....
]
The class
public class LocalizedPageRouteModelConvention : IPageRouteModelConvention
{
private ILocalizationService _localizationService;
public LocalizedPageRouteModelConvention(ILocalizationService localizationService)
{
_localizationService = localizationService;
}
public void Apply(PageRouteModel model)
{
var route = _localizationService.LocalRoutes().FirstOrDefault(p => p.Page == model.RelativePath);
if (route != null)
{
foreach (var option in route.Versions)
{
model.Selectors.Add(new SelectorModel()
{
AttributeRouteModel = new AttributeRouteModel
{
Template = option
}
});
}
}
}
}
To retrieve a RouteData value, you can specify a token within the template for a route. For example, the route {version} would add a RouteData value of version that would be taken from the URL's first segment. In your example, you don't specify a token for version and so there will be no RouteData value for it, as you've described.
The solution for your specific problem is two-part:
Instead of using specific values when creating new SelectorModels, use a token as described above.
With this in place, you will now be able to access a version value from RouteData, but the new problem is that any value can be provided, whether or not it was specified in your configuration.
To solve the second problem, you can turn to IActionConstraint. Here's an implementation:
public class VersionConstraint : IActionConstraint
{
private readonly IEnumerable<string> allowedValues;
public VersionConstraint(IEnumerable<string> allowedValues)
{
this.allowedValues = allowedValues;
}
public int Order => 0;
public bool Accept(ActionConstraintContext ctx)
{
if (!ctx.RouteContext.RouteData.Values.TryGetValue("version", out var routeVersion))
return false;
return allowedValues.Contains((string)routeVersion);
}
}
VersionConstraint takes a list of allowed values (e.g. nyheter, sistenytt) and checks whether or not the version RouteData value matches. If it doesn't match, the "action" (it's really a page at this point) won't be a match and will end up with a 404.
With that implementation in place, you can update your implementation of LocalizedPageRouteModelConvention's Apply to look like this:
var route = _localizationService.LocalRoutes().FirstOrDefault(p => p.Page == model.RelativePath);
if (route != null)
{
model.Selectors.Add(new SelectorModel
{
AttributeRouteModel = new AttributeRouteModel
{
Template = "{version}"
},
ActionConstraints =
{
new VersionConstraint(route.Versions)
}
});
}
This implementation adds a single new SelectorModel that's set up with a Version RouteData value and is constrained to only allow the values specified in configuration.
Related
I am looking into adding an OpenAPI spec to my web API project, but I am running into various obstacles that I am not able to resolve.
API endpoint: /api/some_controller/some_method/id
The content body needs to come from the http body, but I do not want automatic binding using [FromBody] as I need to stream and process the data as-is (auditing, etc).
I added swagger to my project but as expected it does not show a body parameter.
The following DOES generate a proper swagger API definition:
public void some_method([FromBody]MyType mytype);
But as stated before, I need the raw data without model binding.
I am at a loss on how to solve this. Do I need to augment the API explorer somehow? Do I need to add options to swagger? Is there some way to have the [FromBody] attribute that does not actually bind? How can I do this?
I managed to get this to work using an extra custom attribute and an IOperationFilter
[AttributeUsage(AttributeTargets.Method)]
public class OpenApiRequestBodyType: Attribute
{
public Type BodyType { get; }
public string [] ContentTypes { get; }
public OpenApiRequestBodyType(Type type, string[] contentTypes = null)
{
BodyType = type;
ContentTypes = contentTypes;
}
}
public class SwaggerBodyTypeOperationFilter : IOperationFilter
{
public void Apply(OpenApiOperation operation, OperationFilterContext context)
{
var bodyTypeAttribute = context.ApiDescription.CustomAttributes().OfType<OpenApiRequestBodyType>().FirstOrDefault();
if (bodyTypeAttribute != null)
{
var schema = context.SchemaGenerator.GenerateSchema(bodyTypeAttribute.BodyType, context.SchemaRepository);
operation.RequestBody = new OpenApiRequestBody();
string[] contentTypes;
if (bodyTypeAttribute.ContentTypes != null)
contentTypes = bodyTypeAttribute.ContentTypes;
else
contentTypes = operation.Responses.Where(x => x.Key =="200").SelectMany(x=>x.Value.Content).Select(x=>x.Key).ToArray();
foreach (var contentType in contentTypes)
{
operation.RequestBody.Content.Add(KeyValuePair.Create(contentType, new OpenApiMediaType { Schema = schema }));
}
}
}
}
Then I simply tag the method:
[OpenApiRequestBodyType(typeof(my_custom_type))]
and in the Startup:
services.AddSwaggerGen(c =>
{
c.OperationFilter<SwaggerBodyTypeOperationFilter>();
}
I am still not sure if there is no better way to do this.... but at least it works for me...
I have an HTML form that is sent to a controller action (via POST) based on MVC.
The form contains different inputs. There are extra inputs if the user has certain claims.
For example, if the User is administrator, he/she sees an additional text area for comments.
public class MySubmit
{
public string Name { get; set; }
public string IsActive { get; set; }
// only an administrator should be able to set this field
// for all other users, this should be empty
public string Comment { get; set; }
}
public class MyController : Controller
{
public IActionResult MyActionResult(MySubmit submit)
{
}
}
What is the best and safest way to process the result on the action?
Theoretically it is possible that the a tries to submit values although he/she does not actually see the corresponding form controls, because he/she does not have the claim.
I would like to set default values used for field values instead, if the user does NOT have these claims - no matter what values he sends for these fields.
Is there anything built in?
Bryan Lewis gave the right hint: Fluent Validation.
Fluent Validation has the ability to use the HTTP context via Dependency Injection to receive the user and perform a claim comparison:
public class YourModelValidator: AbstractValidator<YourModel>
{
public YourModelValidator(IHttpContextAccessor httpContext)
{
RuleFor(x => x.YourProprty).Custom( (html, context) =>
{
var user = httpContext.User;
if (!user.HasClaim(c => c.Type.Equals(claim))
{
context.AddFailure("Claim is missing.");
}
});
}
}
You can validate the value, but you should not set the value.
Is there anything built in?
No. There's no built-in way to do that.
Design
You might want to achieve that with a custom model binder. But I believe that's not a good way. Because you'll have to process all kinds of input formatters at the same time. Think about somewhere your action expects a [FromForm]MySubmit mySubmit while another action expects a [FromBody] Submit mySubmit. The first action requires a payload of form, while the second action might expect a JSON. Even you take care of the two above scenarios, what about you want to enable XML payloads in future? In short, you can hardly write a general Model Binder for this.
Validation might help. But validation usually makes you repeat yourself if you have several models( Think about you have ten domain models, each one has several properties that requires some claims)
IMO, a better way is to use ActionFilter. Since ActionFilter takes place after the model binding, it would be possible to erase the field when the field requires a role.
To do that, create a custom attribute to mark which property requires some role:
[AttributeUsage(AttributeTargets.Property, AllowMultiple=false)]
internal class RequireRolesForBindingAttribute : Attribute
{
public string[] Roles {get;}
public RequireRolesForBindingAttribute(params string[] roles)
{
this.Roles = roles;
}
}
Now when some roles are required, simply annotate the target property like below:
public class MySubmit
{
public string Name { get; set; }
public string IsActive { get; set; }
// only an root/admin can bind this field for all other users, this should be empty
[RequireRolesForBindingAttribute("root","admin")]
public string Comment { get; set; }
public Sub Sub{get;set;} // test it with a complex child
}
public class Sub{
public int Id {get;set;}
public string Name {get;set;}
[RequireRolesForBindingAttribute("root","admin")]
public string Note {get;set;}
}
The above data annotation represents that the two properties should be erased if the user has no rights:
Comment property of MySubmit
Note property of Sub
Finally, don't forget to enable an custom action filter. For example, add it on action method:
[TypeFilter(typeof(RequireRolesForBindingFilter))]
public IActionResult Test(MySubmit mySubmit)
{
return Ok(mySubmit);
}
An Implementation of RequireRolesForBindingFilter
I create an implementation of RequireRolesForBindingFilter for your reference:
public class RequireRolesForBindingFilter : IAsyncActionFilter
{
private readonly IAuthorizationService _authSvc;
public RequireRolesForBindingFilter(IAuthorizationService authSvc)
{
this._authSvc = authSvc;
}
public async Task OnActionExecutionAsync(ActionExecutingContext context, ActionExecutionDelegate next)
{
// skip early when User ==null,
// if you don't want to allow anonymous access, use `[Authorize]`
if(context.HttpContext.User !=null) {
await this._checkUserRights(context.ActionArguments, context.HttpContext.User);
}
await next();
}
private async Task _checkUserRights(IDictionary<string, object> args, ClaimsPrincipal user){
// handle each argument
foreach(var kvp in args){
if(kvp.Value==null) { return; }
var valueType = kvp.Value.GetType();
if(await _shouldSetNullForType(valueType, user)) {
args[kvp.Key] = valueType.IsValueType? Activator.CreateInstance(valueType) : null;
}else{
// handle each property of this argument
foreach(var pi in valueType.GetProperties())
{
var pv = pi.GetValue(kvp.Value);
await _checkPropertiesRecursive( instanceValue: kvp.Value, propInfo: pi, user: user);
}
}
}
async Task<bool> _shouldSetNullForType(Type type, ClaimsPrincipal user)
{
// the `RequireRolesForBindingAttribute`
var attr= type
.GetCustomAttributes(typeof(RequireRolesForBindingAttribute), false)
.OfType<RequireRolesForBindingAttribute>()
.FirstOrDefault();
return await _shouldSetNullForAttribute(attr,user);
}
async Task<bool> _shouldSetNullForPropInfo(PropertyInfo pi, ClaimsPrincipal user)
{
// the `RequireRolesForBindingAttribute`
var attr= pi
.GetCustomAttributes(typeof(RequireRolesForBindingAttribute), false)
.OfType<RequireRolesForBindingAttribute>()
.FirstOrDefault();
return await _shouldSetNullForAttribute(attr,user);
}
async Task<bool> _shouldSetNullForAttribute(RequireRolesForBindingAttribute attr, ClaimsPrincipal user)
{
if(attr!=null) {
var policy = new AuthorizationPolicyBuilder().RequireRole(attr.Roles).Build();
// does the user have the rights?
var authResult = await this._authSvc.AuthorizeAsync(user, null, policy);
if(!authResult.Succeeded){
return true;
}
}
return false;
}
// check one property (propInfo) for instance `instanceValue`
async Task _checkPropertiesRecursive(object instanceValue, PropertyInfo propInfo, ClaimsPrincipal user){
if(instanceValue == null) return;
Type propType = propInfo.PropertyType;
object propValue = propInfo.GetValue(instanceValue);
if(await _shouldSetNullForPropInfo(propInfo, user))
{
propInfo.SetValue(instanceValue, propType.IsValueType? Activator.CreateInstance(propType) : null);
}
else if( !shouldSkipCheckChildren(propType) && propValue!=null ){
// process every sub property for this propType
foreach(var spi in propType.GetProperties())
{
await _checkPropertiesRecursive(instanceValue: propValue , spi, user );
}
}
bool shouldSkipCheckChildren(Type type) => (type == typeof(string) || type == typeof(DateTime));
}
}
}
Demo:
When some user, who has no rights to submit the comment and note filed, sends a payload as below:
POST https://localhost:5001/home/test
cookie: <my-cookie>
Content-Type: application/x-www-form-urlencoded
name=a&isActive=true&comment=abc&sub.Name=s1&sub.note=magic
The response will be:
HTTP/1.1 200 OK
Connection: close
Content-Type: application/json; charset=utf-8
Server: Kestrel
Transfer-Encoding: chunked
{
"name": "a",
"isActive": "true",
"comment": null,
"sub": {
"id": 0,
"name": "s1",
"note": null
}
}
There are really two actions that you want to perform -- validation and conditional editing of the incoming model. For complex validation, you should consider using something like FluentValidation (https://fluentvalidation.net/), which is quite flexible, integrates with MVC's ModelState and will allow you to check all sorts of things based on conditions. It's not clear from your post if you referring to "claims" in the general sense or specifically to ASP.Net Identity Claims -- either way, you can pull identity information into the FluentValidation Validator and create conditional checks based on identity information. The validators (FV or otherwise) don't really handle resetting/editing the model. For your example, you can simply alter the model directly after the validation is complete.
// if Validation is successful
if (isAdmin) { // however you are checking their role
submit.Comment = null; // or whatever the default value should be
}
// Do something with the incoming model / dave to DB etc
I have a collection of BsonDocuments, for example:
MongoCollection<BsonDocument> products;
When I do inserts into the collection, I want the member name to always be lowercase. After reading the documentation, it appears that ConventionPack is the way to go. So, I've defined one like this:
public class LowerCaseElementNameConvention : IMemberMapConvention
{
public void Apply(BsonMemberMap memberMap)
{
memberMap.SetElementName(memberMap.MemberName.ToLower());
}
public string Name
{
get { throw new NotImplementedException(); }
}
}
And right after I get my collection instance I register the convention like this:
var pack = new ConventionPack();
pack.Add(new LowerCaseElementNameConvention());
ConventionRegistry.Register(
"Product Catalog Conventions",
pack,
t => true);
Unfortunately, this has zero effect on what is stored in my collection. I debugged it and found that the Apply method is never called.
What do I need to do differently to get my convention to work?
In order to use IMemeberMapConvention, you must make sure to declare your conventions before the mapping process takes place. Or optionally drop existing mappings and create new ones.
For example, the following is the correct order to apply a convention:
// first: create the conventions
var myConventions = new ConventionPack();
myConventions.Add(new FooConvention());
ConventionRegistry.Register(
"My Custom Conventions",
myConventions,
t => true);
// only then apply the mapping
BsonClassMap.RegisterClassMap<Foo>(cm =>
{
cm.AutoMap();
});
// finally save
collection.RemoveAll();
collection.InsertBatch(new Foo[]
{
new Foo() {Text = "Hello world!"},
new Foo() {Text = "Hello world!"},
new Foo() {Text = "Hello world!"},
});
Here's how this sample convention was defined:
public class FooConvention : IMemberMapConvention
private string _name = "FooConvention";
#region Implementation of IConvention
public string Name
{
get { return _name; }
private set { _name = value; }
}
public void Apply(BsonMemberMap memberMap)
{
if (memberMap.MemberName == "Text")
{
memberMap.SetElementName("NotText");
}
}
#endregion
}
These are the results that came out when I ran this sample. You could see the Text property ended up being saved as "NotText":
If I understand correctly, conventions are only applied when auto-mapping. If you have a classmap, you need to explicitly call AutoMap() to use conventions. Then you can modify the automapping, e.g.:
public class MyThingyMap : BsonClassMap<MyThingy>
{
public MyThingyMap()
{
// Use conventions to auto-map
AutoMap();
// Customize automapping for specific cases
GetMemberMap(x => x.SomeProperty).SetElementName("sp");
UnmapMember(x => x.SomePropertyToIgnore);
}
}
If you don't include a class map, I think the default is to just use automapping, in which case your convention should apply. Make sure you're registering the convention before calling GetCollection<T>.
You can define ConventionPack which is also part of their official document on Serialization. Like below which stores are property names as camel case. You can place while Configuring services/repositories
Official link
https://mongodb.github.io/mongo-csharp-driver/1.11/serialization/[Mongo Db Serialization C#]1
// For MongoDb Conventions
var pack = new ConventionPack
{
new CamelCaseElementNameConvention()
};
ConventionRegistry.Register(nameof(CamelCaseElementNameConvention), pack, _ => true);
Given a validator class that looks like this
public class SomeValidator : AbstractValidator<SomeObject>
{
public SomeValidator(){
RuleSet("First",
() => {
RuleFor(so => so.SomeMember).SetValidator(new SomeMemberValidator())
});
RuleSet("Second",
() => ... Code Does Not Matter ... );
RuleSet("Third",
() => ... Code Does Not Matter ... );
}
}
And another to do the inner member validation
public class SomeMemberValidator: AbstractValidator<SomeMember>
{
public SomeValidator(){
RuleSet("Fourth",
() => {
... Code Does Not Matter ...
});
}
}
Question is, I want to run specific rulesets: "First", "Second", and "Fourth". I don't want "Third" to run.
Given the Validate method signature only takes a single ruleset argument I don't see any way to do this. There is "*", but I don't want to run all the rules.
Please help.
You could use validator constructor instead of RuleSet as a workaround for this problem.
Just create enum inside of validator class and then use its value when creating validator.
I this way correct rules will be activated depending on what Mode is set in constructor.
public class UserValidator : AbstractValidator<User>
{
public enum Mode
{
Create,
Edit
}
public UserValidator()
{
// Default rules...
}
public UserValidator(UserValidator.Mode mode)
: this()
{
if (mode == Mode.Edit)
{
// Rules for Edit...
RuleFor(so => so.SomeMember)
.SetValidator(
new SomeMemberValidator(SomeMemberValidator.Mode.SomeMode))
}
if (mode == Mode.Create)
{
// Rules for Create...
RuleFor(so => so.SomeMember)
.SetValidator(
new SomeMemberValidator())
}
}
}
I think it's actually more flexible method than using RuleSet.
There is only one small problem regarding FluentValidation MVC integration:
User class can't have attribute [Validator(typeof(UserValidator))] because UserValidator will be then created using default constructor, before you can do anything in controller method.
Validator must be created and called manually. Like that for example:
public class UserController : Controller
{
[HttpPost]
public ActionResult Create(User userData)
{
var validator = new UserValidator(UserValidator.Mode.Create);
if (ValidateWrapper(validator, userData, this.ModelState))
{
// Put userData in database...
}
else
{
// ValidateWrapper added errors from UserValidator to ModelState.
return View();
}
}
private static bool ValidateWrapper<T>(FluentValidation.AbstractValidator<T> validator, T data, ModelStateDictionary modelState)
{
var validationResult = validator.Validate(data);
if (!validationResult.IsValid)
{
foreach (var error in validationResult.Errors)
modelState.AddModelError(error.PropertyName, error.ErrorMessage);
return false;
}
return true;
}
}
You can execute more than one RuleSet, but I don't think you can execute the inner RuleSet.
validator.Validate(new ValidationContext<SomeObject>(person, new PropertyChain(), new RulesetValidatorSelector("First", "Second", "Fourth")));
Source
The other option is to investigate the source code and try to think a way of doing it. The third parameter of the ValidationContext is an interface, IValidatorSelector, maybe you can have some luck with a custom class.
The platform is ASP.NET MVC 2.
We have a user story that states:
On the [view], don't allow a user to edit [property] unless the user is
a [proper role]. They still must be able to view the [property].
So, I must show the field for these people, just prevent them from changing or updating the property value.
I know that I can place a read only control in the view using an attribute for the current user. That should give the client a visual cue that edits are not permitted. But a CSS style won't prevent someone from hacking their post to alter the property's value.
My question pertains to protecting the property on the server side. What methods can I employ to detect changes to my incoming view model in this situation -- where a user can't edit a certain property?
EDIT
I would need to stay away from binds and whitelists -- I appreciate the ideas! They caused me to realize that I omitted a key piece of information.
My product owner wishes to add properties willy-nilly and at their pleasure -- which I took to read: non-static solutions need not apply. Additionally, she wishes to apply other conditional logic to their application -- "if the state of a related property is 'X', then they can edit regardless of permission", etc. I can handle that part. I just need to know where to dynamically apply them.
I am thinking that this is a custom model binder solution.
BTW, we append this particular permission to the roles:
var hasPermission = User.IsInRole(permission);
You could use the Bind attribute that lets you specify properties that are to be included or excluded on binding. here is a good basic article for more information.
Exclude attributes using Bind attribute
I decided that the custom model binder was the way to go. I can already disable the HTML controls. But I need to selectively authorize them.
I know the example object is contrived -- surely, you wouldn't have users post an object with two uneditable properties -- but the point is that I don't want to allow the user to persist their value. I will NULL out any value and then not update any NULL values. By ignoring NULL values, I don't have to go to the data access to retrieve the current value for replacement of the offending update.
This code has me on my way (using MSPEC as the testing framework):
public class TestSplitDetailViewModel
{
public int Id { get; set; }
[CanEdit]
public string RestrictedProperty { get; set; }
}
[AttributeUsage(AttributeTargets.Property, AllowMultiple = false, Inherited = false)]
public class CanEditAttribute : Attribute
{
}
public class CanEditAttributeBinder : DefaultModelBinder
{
private readonly ISecurityTasks _securityTask;
private readonly ISecurityContext _securityContext;
public CanEditAttributeBinder(ISecurityTasks securityTask, ISecurityContext securityContext)
{
this._securityTask = securityTask;
this._securityContext = securityContext;
}
protected override void BindProperty(ControllerContext controllerContext, ModelBindingContext bindingContext, PropertyDescriptor propertyDescriptor)
{
var canEditAttribute = propertyDescriptor.Attributes
.OfType<CanEditAttribute>()
.FirstOrDefault();
if (canEditAttribute != null)
{
bool allowed = IsAllowed();
if (allowed)
{
propertyDescriptor.SetValue(bindingContext.Model, null);
}
else
{
base.BindProperty(controllerContext, bindingContext, propertyDescriptor);
}
}
else
{
base.BindProperty(controllerContext, bindingContext, propertyDescriptor);
}
}
private bool IsAllowed()
{
return !this._securityTask.DoesUserHaveOperation(this._securityContext.User.Username, UserOperations.ReclassAllowed);
}
}
public class TestModelSpec : Specification<CanEditAttributeBinder>
{
protected static HomeController controller;
private static MockRepository mocks;
protected static ISecurityTasks securityTasks;
private static ISecurityContext securityContext;
protected static ModelBindingContext bindingContext;
Establish context = () =>
{
ServiceLocatorHelper.AddUserServiceWithTestUserContext();
securityTasks = DependencyOf<ISecurityTasks>().AddToServiceLocator();
securityContext = DependencyOf<ISecurityContext>().AddToServiceLocator();
user = new User("CHUNKYBACON");
securityContext.User = user;
// When we restricted access on the client,
// Chunky submitted a FORM POST in which he HACKED a value
var formCollection = new NameValueCollection
{
{ "TestSplitDetailViewModel.Id", "2" },
{ "TestSplitDetailViewModel.RestrictedProperty", "12" } // Given this is a hacked value
};
var valueProvider = new NameValueCollectionValueProvider(formCollection, null);
var modelMetadata = ModelMetadataProviders.Current.GetMetadataForType(null, typeof(TestSplitDetailViewModel));
bindingContext = new ModelBindingContext
{
ModelName = "TestSplitDetailViewModel",
ValueProvider = valueProvider,
ModelMetadata = modelMetadata
};
controller = new HomeController(null, null, null, null, null);
mocks = new MockRepository();
MvcMockHelpers.SetFakeControllerContext(mocks, controller);
};
protected static User user;
protected static TestSplitDetailViewModel incomingModel;
}
public class when_a_restricted_user_changes_a_restricted_property : TestModelSpec
{
private Establish context = () => securityTasks.Stub(st =>
st.DoesUserHaveOperation(user.Username, UserOperations.ReclassAllowed)).Return(false);
Because of = () => incomingModel = (TestSplitDetailViewModel)subject.BindModel(controller.ControllerContext, bindingContext);
It should_null_that_value_out = () => incomingModel.RestrictedProperty.ShouldBeNull();
}
public class when_an_unrestricted_user_changes_a_restricted_property : TestModelSpec
{
private Establish context = () => securityTasks.Stub(st =>
st.DoesUserHaveOperation(user.Username, UserOperations.ReclassAllowed)).Return(true);
Because of = () => incomingModel = (TestSplitDetailViewModel)subject.BindModel(controller.ControllerContext, bindingContext);
It should_permit_the_change = () => incomingModel.RestrictedProperty.ShouldEqual("12");
}
EDIT
This is now my answer. I see where some might question my testing DefaultModelBinder.BindProperty. I am testing my custom override.
I would use either a whitelist or blacklist and invoke model binding explicitly on your model. e.g.
[HttpPost]
public ActionResult Edit(int id) {
var item = db.GetByID(id); // get from DB
var whitelist = [ "Name", "Title", "Category", etc ]; // setup the list of fields you want to update
UpdateModel(item, whitelist);
}