I have this action in my RESTful application on MVC3:
[HttpPut]
public void Rest(ViewModel view_model, int id)
{
//doing something with view_model
}
Where ViewModel class is a class for passing data to/from client Javascript (I don`t want to pass pure DB entities):
public class ViewModel
{
public ViewModel() //parameterless constructor, needed for accepting as parameter in action
{
}
public ViewModel(Model m)
{
id = m.ID;
Title = m.Title;
}
public int? id { get; set; }
private string _title;
public string Title
{
get
{
if (String.IsNullOrWhiteSpace(_title)) throw new Exception("Empty field");
return _title;
}
set
{
_title = value;
}
}
}
BUT when I make PUT request with that data:
{ "id" : 7, "Title" : "Hello world!" }
I get that "Empty field" exception. Seems like something is trying to get Title property, even before it has been set with incoming "Hello world!" data.
Why?
And where can I get some information, how this whole operation works, i.e. looking for object ViewModel that specified as action parameter, in actual XHR-request.
Thank you for your thoughts.
Seems like something is trying to get Title property, even before it
has been set with incoming "Hello world!" data. Why?
It is the default model binder. And more specifically the BindProperty method. This method is called during binding and it uses reflection to call the getter. Because the model binder recurses down the object hierarchy graph it first needs to get the value of the property, build a binding context and model metadata for each property and then invoke the SetProperty method.
If you don't want the title property to be empty use the validation mechanisms and auto implemented properties:
[Required]
public string Title { get; set; }
and then in your RESTful action check if the ModelState.IsValid. It is much more easier and MVCish:
[HttpPut]
public ActionResult Rest(ViewModel view_model, int id)
{
if (!ModelState.IsValid)
{
...
}
// doing something with view_model
...
}
Related
Is it possible to pass into the ModelBinder which implementation you want to use inline?
Given the following definitions:
public interface ISomeInterface
{
string MyString{get;set;}
}
public class SomeInterfaceImplementation_One : ISomeInterface
{
private string _MyString;
public string MyString
{
get {return "This is implementation One " + _MyString ; }
set { _MyString = value; }
}
}
public class SomeInterfaceImplementation_Two : ISomeInterface
{
private string _MyString;
public string MyString
{
get {return "This is implementation Two" + _MyString ; }
set { _MyString = value; }
}
}
Given this route in asp.net mvc core:
public ActionResult InterfaceWithInlineImplementation([ModelBinder(typeof(SomeBinder))]ISomeInterface SomeInterface)
{
//Return actionresult
}
I do not want a different ModelBinder class for each implementation rather I would like each route to specify which implementation inline.
So something like:
[UseImplementation(SomeInterfaceImplementation_One)]
public ActionResult InterfaceWithInlineImplementation([ModelBinder(typeof(SomeBinder))]ISomeInterface SomeInterface)
{
}
Or:
public ActionResult InterfaceWithInlineImplementation([ModelBinder(typeof(SomeBinder), ConcreteType = SomeInterfaceImplementation_Two )]ISomeInterface SomeInterface)
{
}
This way the SomeBinder class can access which implementation is being requested in the BindModelAsync method of SomeBinder : IModelBinder class.
public class SomeBinder : Microsoft.AspNetCore.Mvc.ModelBinding.IModelBinder
{
public Task BindModelAsync(Microsoft.AspNetCore.Mvc.ModelBinding.ModelBindingContext bindingContext)
{
if (bindingContext == null)
throw new ArgumentNullException(nameof(bindingContext));
string valueFromBody = string.Empty;
using (var sr = new StreamReader(bindingContext.HttpContext.Request.Body))
{
valueFromBody = sr.ReadToEnd();
}
if (string.IsNullOrEmpty(valueFromBody))
{
return Task.CompletedTask;
}
var settings = new JsonSerializerSettings()
{
ContractResolver = new InterfaceContractResolver(), // Need requested implementation from InterfaceWithInlineImplementation() method
};
var obj = JsonConvert.DeserializeObject(valueFromBody, [**Need Requested Implementation from Method**], settings);
bindingContext.Model = obj;
bindingContext.Result = ModelBindingResult.Success(obj);
return Task.CompletedTask;
}
Use generics.
public class SomeBinder<TConcreteType> : IModelBinder
{
}
Then your signature becomes
public ActionResult InterfaceWithInlineImplementation(
[ModelBinder(typeof(SomeBinder<SomeInterfaceImpelemtation_One>))]ISomeInterface SomeInterface)
Then deserialization is:
JsonConvert.DeserializeObject<TConcreteType>(json)
However based on your last comment it sounds like you just need to Prevent overposting instead of this convoluted model binding.
So lets say the client knows that the server implementation has security methods and tries to match the signature hoping everything get deseriazled for example. Its being explicit as to what you're expecting. And you're explicitly expecting only the contract definition and nothing more.
Excerpt:
Mass assignment typically occurs during model binding as part of MVC. A simple example would be where you have a form on your website in which you are editing some data. You also have some properties on your model which are not editable as part of the form, but instead are used to control the display of the form, or may not be used at all.
public class UserModel
{
public string Name { get; set; }
public bool IsAdmin { get; set; }
}
So the idea here is that you only render a single input tag to the markup, but you post this to a method that uses the same model as you used for rendering:
[HttpPost]
public IActionResult Vulnerable(UserModel model)
{
return View("Index", model);
}
However, with a simple bit of HTML manipulation, or by using Postman/Fiddler , a malicious user can set the IsAdmin field to true. The model binder will dutifully bind the value, and you have just fallen victim to mass assignment/over posting:
So how can you prevent this attack? Luckily there's a whole host of different ways, and they are generally the same as the approaches you could use in the previous version of ASP.NET. I'll run through a number of your options here.
Continue to article...
I'm using ValueInjecter to flatten/unflatten view models into domain objects created by Entity Framework (4.3.1) model-first. All of my VARCHAR columns in my database are NOT NULL DEFAULT '' (personal preference, no desire to open up a holy war here). On post, the view model comes back with any string property that has no value as null, so when I attempt to inject it back into my domain model class, EF barks at me for attempting to set a property with IsNullable=false to null. Example (over-simple):
public class ThingViewModel
{
public int ThingId{get;set;}
public string Name{get;set;}
}
public class Thing
{
public global::System.Int32 ThingId
{
//omitted for brevity
}
[EdmScalarPropertyAttribute(EntityKeyProperty=false, IsNullable=false)]
[DataMemberAttribute()]
public global::System.String Name
{
//omitted for brevity
}
}
Then, my controller post looks like this:
[HttpPost]
public ActionResult Edit(ThingViewModel thing)
{
var dbThing = _thingRepo.GetThing(thing.ThingId);
//if thing.Name is null, this bombs
dbThing.InjectFrom<UnflatLoopValueInjection>(thing);
_thingRepo.Save();
return View(thing);
}
I'm using UnflatLoopValueInjection because I have nested types in the actual domain version of Thing. I attempted to write a custom ConventionInjection to convert null strings to string.Empty, but it appears that UnflatLoopValueInjection switches it back to null. Is there a way I can get ValueInjecter not to do this?
Nuts, I just figured it out with help from the wiki. The solution appears to be to extend UnflatLoopValueInjection:
public class NullStringUnflatLoopValueInjection : UnflatLoopValueInjection<string, string>
{
protected override string SetValue(string sourceValue)
{
return sourceValue ?? string.Empty;
}
}
Below, in CreateTest, uponsuccessful, I want to redirect to Tests from CreateTest.
I want to do something like the following:
public ActionResult Tests(int ID, string projectName)
{
TestModel model = new TestModel (ID, projectName);
return View(model);
}
[HttpPost]
public ActionResult CreateTest(TestModel model)
{
try
{
return RedirectToAction("Tests");
}
catch (Exception e)
{
ModelState.AddModelError("Error", e.Message);
return View(model);
}
}
You might need to provide the arguments when redirecting:
return RedirectToAction("Tests", new {
ID = model.ID,
projectName = model.ProjectName
});
and the url you will be redirecting to will now look something like this:
/Foo/Tests?ID=123&projectName=abc
I know this is a bit old but...
What I've done in the past is have a "MessageArea" class exposed as a property on my base controller that all my controllers ultimately inherit from. The property actually stores the class instance in TempData. The MessageArea has a method to Add() which takes a string message and an enum Type (e.g. Success, Error, Warning, Information).
I then have a partial that renders whatever messages are in MessageArea with appropriate styling according to the type of the message.
I have a HTMLHelper extension method RenderMessageArea() so in any view I can simple say #Html.RenderMessageArea(), the method and partial take care of nulls and nothing is output if there are no messages.
Because data stored in TempData only survives 1 request it is ideal for cases where you want your action to redirect but have 1 or more messages shown on the destination page, e.g. an error, not authorised page etc... Or if you add an item but then return to the index list page.
Obviously you could implement something similar to pass other data. Ultimately I'd say this is a better solution to the original question than the accepted answer.
EDIT, EXAMPLE:
public class MessageAreaModel {
public MessageAreaModel() {
Messages = new List<Message>();
}
public List<Message> Messages { get; private set; }
public static void AddMessage(string text, MessageIcon icon, TempDatadictionary tempData) {
AddMessage(new Message(icon, text), tempData);
}
public static void AddMessage(Message message, TempDataDictionary tempData) {
var msgArea = GetAreaModelOrNew(tempData);
msgArea.Messages.Add(message);
tempData[TempDataKey] = msgArea;
}
private static MessageAreaModel GetAreaModelOrNew(TempDataDictionary tempData) {
return tempData[TempDataKey] as MessageAreaModel ?? new MessageAreaModel();
}
The above class can then be used to add messages from your UI layer used by the controllers.
Then add an HtmlHelper extension like so:
public static void RenderMessageArea(this HtmlHelper html) {
html.RenderPartial("MessageArea",
(MessageAreaModel)html.ViewContext.TempData[MessageAreaModel.TempDataKey] ?? MessageAreaModel.Empty);
html.ViewContext.TempData.Remove(MessageAreaModel.TempDataKey);
}
The above is not fully completed code there are various bells and whistles I've left out but you get the impression.
Make the int nullable:
public ActionResult Tests(int? ID, string projectName){
//...
}
Say I have a model like so:
public class MyViewModel {
//some properties
public string MyString {get;set;}
public Dictionary<string,string> CustomProperties {get;set;}
}
And I am presenting the dictionary property like this:
<%= Html.EditorFor(m => m.CustomProperties["someproperty"]) %>
All is working well, however I have implemented a custom validator to validate the properties of this dictionary, but when returning a ModelValidationResult I can not get the member name referenced correctly (which chould be CustomProperties[someproperty] I believe). All the items in the list which are properties are bound correctly to their errors (I want the error class in the text box so I can highlight it).
Here is my code for the custom validator so far
public class CustomValidator : ModelValidator
{
public Custom(ModelMetadata metadata, ControllerContext controllerContext) : base(metadata, controllerContext)
{
}
public override IEnumerable<ModelValidationResult> Validate(object container)
{
if (Metadata.PropertyName.Equals("mystring", StringComparison.OrdinalIgnoreCase))
{
yield return new ModelValidationResult() {Message = "normal property validator works!!"};
}
else if (Metadata.PropertyName.Equals("customproperties", StringComparison.OrdinalIgnoreCase))
{
yield return new ModelValidationResult() { MemberName = "CustomProperties[someproperty]", Message = "nope!" };
}
}
}
It appears like something is filling in the MemberName property further up, and ignoring what I put in there
Cheers,
Amar
It appears to me that you are making validation more difficult than it needs to be. Have you taken a look at DataAnnotations which are built into the framework? Scott Gu's blog talks about this. It's a really nice (and easy) way to do validation of models.
I have a View for creating a customer that contains numerous textboxes. After the user tabs out of each textbox I want to use JQuery to call a Controller method that will check in the DataBase and look for any possible matches, the controller will then send content and I will use jQuery to dynamically show the possible matches (Similar to what Stack Overflow does when you enter in your question and shows Related Questions).
My question is, I have 15 textboxes and would like to send that data from each back with each call. I'd like to avoid having my Controller method with a signature like
Public ActionResult CheckMatches(string param1, string param2... string param15)
Is there an easier way to pass multiple paramers as a single object, like FormCollection?
All you need to do is create a type with properties the same name as the names of your textboxes:
public class CheckMatchesAguments
{
public string Param1 { get; set; }
public string Param2 { get; set; }
// etc.
}
Then change your action to:
public ActionResult CheckMatches(CheckMatchesAguments arguments)
That's all!
Be warned, though: If CheckMatchesAguments has any non-nullable properties (e.g., ints), then values for those properties must be in the FormCollection, or the default model binder won't bind anything in the type. To fix this, either include those properties, too, in the form, or make the properties nullable.
Javascript:
var data = { foo: "fizz", bar: "buzz" };
$.get("urlOfAction", data, callback, "html")
Action:
public ActionResult FooAction(MegaParameterWithLotOfStuff param)
And a custom model binder:
public class MegaParameterWithLotOfStuffBinder : IModelBinder
{
public object GetValue(ControllerContext controllerContext,
string modelName, Type modelType,
ModelStateDictionary modelState)
{
var param = new MegaParameterWithLotOfStuff();
param.Foo = controllerContext.
HttpContext.Request.Form["foo"];
param.Bar = controllerContext.
HttpContext.Request.Form["bar"];
return customer;
}
}
Global.asax:
protected void Application_Start()
{
ModelBinders.Binders[typeof(MegaParameterWithLotOfStuff)] =
new MegaParameterWithLotOfStuffBinder();
}
Or binding filter+action combo:
public class BindMegaParamAttribute: ActionFilterAttribute
{
public override void OnActionExecuting
(ActionExecutingContext filterContext)
{
var httpContext = filterContext.HttpContext;
var param = new MegaParameterWithLotOfStuff();
param.Foo = httpContext.Request.Form["foo"];
param.Bar = httpContext.Request.Form["bar"];
filterContext.ActionParameters["param"] = param;
base.OnActionExecuted(filterContext);
}
}
Action:
[BindMegaParam]
public ActionResult FooAction(MegaParameterWithLotOfStuff param)