Model Binding: Safe cast returns null [duplicate] - c#

This question already has answers here:
Is it possible to assign a base class object to a derived class reference with an explicit typecast?
(31 answers)
Closed 5 years ago.
I have the following snippet.
public async Task<ActionResult> Register(RegisterViewModel model)
{
if (!ModelState.IsValid) return View(model as LocalRegisterViewModel);
var user = new User
{
UserId = model.Username,
Password = null,
Email = model.Email,
AccType = model.AccountType
};
var modelAsLocalRegisterViewModel = model as LocalRegisterViewModel;
if (modelAsLocalRegisterViewModel != null)
user.Password = modelAsLocalRegisterViewModel.Password;
//...
}
The classes looks as follows.
public class RegisterViewModel
{
public string Username { get; set; }
public string Email { get; set; }
public int AccountType { get; set; }
}
public interface IInternalPassword
{
string Password { get; set; }
string ConfirmPassword { get; set; }
}
public class LocalRegisterViewModel : RegisterViewModel, IInternalPassword
{
public string Password { get; set; }
public string ConfirmPassword { get; set; }
}
The LocalRegisterViewModel is passed to the controller as follows from a cshtml page.
#model LocalRegisterViewModel
#{
ViewBag.Title = "Register";
Layout = "~/Views/Shared/_LayoutAnonymous.cshtml";
}
<h2>#ViewBag.Title.</h2>
#using (Html.BeginForm("Register", "Account", FormMethod.Post, new { #class = "form-horizontal", role = "form" }))
My problem is that, modelAsLocalRegisterViewModel is null after the safe cast.
var modelAsLocalRegisterViewModel = model as LocalRegisterViewModel;
if (modelAsLocalRegisterViewModel != null)
user.Password = modelAsLocalRegisterViewModel.Password;
Can someone look into this and tell me why?
EDIT
Seems like my questioning style is bad. So let me clarify my exact intention as well. The Register action I have written is intended to serve multiple ViewModels, each having some additional info. So what I have done is writing a parent which carries the common attributes and extending that to get the added attributes. For an instance, I pass an instance of a LocalRegisterViewModel to the controller, so that it will first execute the common functionality and if the instance passed is of type LocalRegisterViewModel if will carry out the extended functionality. That's why I need to check the passed RegisteredViewModel is also of type LocalRegisterViewModel.
EDIT 2
This is not trying to assign a base class instance to a derived class reference. It's a fact that following is completely valid in C#.
class Program
{
static void Test(Parent p)
{
var c = p as Child;
Console.WriteLine(c == null ? "Can't do it!" : "Can do it!");
Console.WriteLine(c.GetType().ToString());
}
static void Main(string[] args)
{
var c = new Child();
Test(c);
}
}
public class Parent
{
}
public class Child : Parent
{
}

Your confusion I think comes from thinking that you are "calling" controller method Register() from cshtml page, and "passing" your model there. It's not exactly true.
When you submit your form, it will post all inputs to server, to the specified url. Those inputs might include properties of LocalRegisterViewModel, such as Password. Request body might look like this:
{"email": "my#email.com", "password": "bla" }
When request comes to server, ASP.NET looks for controller action matching given url. It sees that matching action is Register() and this action accepts parameter of type RegisterViewModel. Now it tries to bind that model (fill its properties from http request). It has absolutely no idea that there are additional values, such as Password, in incoming request.
So asp.net will create instance of RegisterViewModel and fill its properties, ignoring all the rest (such as password), because there is no information in request itself about which C# type it should be parsed into.

Right; so if:
var modelAsLocalRegisterViewModel = model as LocalRegisterViewModel;
gives null, then there are exactly 2 options:
model is null
model is something, but something other than LocalRegisterViewModel
So: you'll need to look at model and find out what it is. We can't tell you that: it isn't in the code shown. But string typeName = model?.GetType()?.Name; should tell you which; it'll return either null or the name of the type that model is.
With the recent edit, we can see that model is a RegisterViewModel; but: it sounds like it isn't a LocalRegisterViewModel. Since there is an inheritance tree, it sounds like model is either the base-type (RegisterViewModel) or a different sub-type unrelated to LocalRegisterViewModel.

Related

Model value redirected to another action does not pass all values

I have an ASP.Net Core application which needs passing of a model from one action to another.
These are models :
public class ClassA
{
public string Id{get;set;}
public string Name {get;set;}
public StudentMarks Marks {get;set;}
}
public class StudentMarks
{
public int Marks {get;set;}
public string Grade {get;set;}
}
And the post Controller:
[HttpPost]
public ActionResult TestAction1(ClassA model)
{
return RedirectToAction("TestAction2", model);
}
public ActionResult TestAction2(ClassA model)
{
}
In TestAction 1 while debugging, i see that Id, Name and marks have value.
I am getting the value for Id in TestAction2 same as that in TestAction1. However the value of complex object Marks is not obtained in the TestAction2 action method.
What are my other options?
You cannot redirect with a model. A redirect is simply an empty response with a 301, 302, or 307 status code, and a Location response header. That Location header contains the the URL you'd like to redirect the client to.
The client then must make a new request to that URL in the header, if it so chooses. Browsers will do this automatically, but not all HTTP clients will. Importantly, this new request is made via a GET, and GET requests do not have bodies. (Technically, the HTTP spec allows for it, but no browser or HTTP client out there actually supports that.)
It's unclear what your ultimate goal is here, but if you need to persist data temporarily between requests (such as a redirect), then you should serialize that data into a TempData key.
You can use TempData to pass model data to a redirect request in Asp.Net Core In Asp.Net core, you cannot pass complex types in TempData. You can pass simple types like string, int, Guid etc. If you want to pass a complex type object via TempData, you have can serialize your object to a string and pass that. I have made a simple test application that will suffice to your needs:
Controller:
public ActionResult TestAction1(ClassA model)
{
model.Id = "1";
model.Name = "test";
model.Marks.Grade = "A";
model.Marks.Marks = 100;
var complexObj = JsonConvert.SerializeObject(model);
TempData["newuser"] = complexObj;
return RedirectToAction("TestAction2");
}
public ActionResult TestAction2()
{
if (TempData["newuser"] is string complexObj )
{
var getModel= JsonConvert.DeserializeObject<ClassA>(complexObj);
}
return View();
}
Model:
public class ClassA
{
public ClassA()
{
Marks = new StudentMarks();
}
public string Id { get; set; }
public string Name { get; set; }
public StudentMarks Marks { get; set; }
}
public class StudentMarks
{
public int Marks { get; set; }
public string Grade { get; set; }
}
If you want to persist your TempData values for more requests you can use Peek and Keep functions. This answer can give more insight on these functions.
I think you're getting model and routeValues mixed up. The overload of RedirectToAction that you're calling (takes a string and an object) expects a routeValues argument, not a model argument. https://learn.microsoft.com/en-us/dotnet/api/microsoft.aspnetcore.mvc.controllerbase.redirecttoaction?view=aspnetcore-2.2
TestAction1 is called via Post, but TestAction2 is called via Get. You need to work out a URL that will let you call TestAction2 the way you want (independently of the RedirectToAction in TestAction1). I'm guessing this will involve setting up a custom route. Once you have a URL that will let you call TestAction2 the way you want, you can specify the route values to form that URL in the RedirectToAction in TestAction1.
I think the problem is that you shuold use:
return RedirectToAction("TestAction2", model);
(you did this without return)

Create DropDownList for an ASP.NET MVC5 Relation [duplicate]

This question already has answers here:
Populating a razor dropdownlist from a List<object> in MVC
(9 answers)
Closed 8 years ago.
I'm stuck creating a proper create/edit view in ASP.NET MVC5. I've got two models Dog and Human. A dog belongs to one Human. I'm trying to create a dropdown in the create and edit views for Dog that'll allow me to select a Human by name for that particular Dog. Here are my models:
Human:
public class Human
{
public int ID { get; set; }
public string Name { get; set; }
}
Dog:
public class Dog
{
public int ID { get; set; }
public string Name { get; set; }
public Human Human { get; set; }
}
My create action:
// GET: /Dog/Create
public ActionResult Create()
{
ViewBag.HumanSelection = db.Humen.Select(h => new SelectListItem
{
Value = h.ID.ToString(),
Text = h.Name
});
return View();
}
And here is the relevant part of my view:
<div class="form-group">
#Html.LabelFor(model => model.Human.Name, new { #class = "control-label col-md-2" })
<div class="col-md-10">
#Html.DropDownListFor(model => model.Human, ViewBag.HumanSelection);
</div>
</div>
I get the following error when I run this:
Compiler Error Message: CS1973: 'System.Web.Mvc.HtmlHelper<Test.Models.Dog>' has no applicable method named 'DropDownListFor' but appears to have an extension method by that name. Extension methods cannot be dynamically dispatched. Consider casting the dynamic arguments or calling the extension method without the extension method syntax.
I'm new to C# & the Entity framework. What am I doing wrong? Is there a way of doing this without manually querying the database? Something like the collection form helpers in Rails?
I've followed a bunch of tutorials that are either old or too complicated for me to follow.
Important to note is that if you use DropDownListFor(x => x.Human), the returned value of the dropdownlist should be a Human object.
It isn't. In your own code snippet, you set the value of the SelectListItem to the ID of the Human. Therefore, when you submit your form, you will receive the ID that you selected.
Add the following to your model:
public int HumanId { get; set; }
Bind your dropdownlist to that int:
#Html.DropDownListFor(model => model.HumanId, (SelectList)ViewBag.HumanSelection);
Now, when you get back to the controller, use that ID to look up the actual Human you want:
[HttpPost]
public ActionResult Create (CreateModel model)
{
if(model.HumanId > 0)
{
model.Human = GetHumanByID(model.HumanId);
//or however you want to get the Human entoty from your database
}
}
It's a simplified solution, but I suspect your main confusion stems from the fact that you're expecting to receive a Human from the DropDownList, while it will actually only return an int (the ID).
Edit
I don't have much information on your data model, but if you're using entity framework, odds are that your Dog class will have a foreign key property called HumanId. If that is the case, you don't even need to get the Human entity like I showed you before. If you put the selected ID in the HumanId property, Entity Framework should be able to use that to create the relation between Human/Dog you want.
If this is the case, it would seems best to elaborate on this in your question, as this would otherwise be more guesswork than actual confirmation.
Edit 2 going offtopic here
Your code:
db.Humen
The plural form of man is men, woman is women; but for human, it's humans :) Humen does sounds like an awesome suggestion though ;)
The problem is that you are attempting to bind a Human type to a dropdown in the UI, a dropdown whose values are strings (the IDs of Human instances) and text are also strings (the names of Human instances).
What you should be binding to the dropdown instead is the ID of the Human, to match the fact that the ID is being used as the value. So with a view model such as
public class CreateDogModel
{
public string Name { get; set; }
[Range(0, int.MaxValue)]
public int Human { get; set; }
public IEnumerable<Human> Humans { get; set; }
}
And the GET controller action
[HttpGet]
public ActionResult Create()
{
var model = new CreateDogModel
{
Humans = db.Human.ToList()
};
return View(model);
}
The view then becomes
#Html.DropDownListFor(
model => model.Human,
Model.Humans.Select(h => new SelectListItem
{
Text = h.Name,
Value = h.ID.ToString()
}),
"Please select a Human");
In your POST controller action, you now look up the chosen human by the Human property value from the View model
[HttpPost]
public ActionResult Create(CreateDogModel model)
{
if (!ModelState.IsValid)
{
// fetch the humans again to populate the dropdown
model.Humans = db.Human.ToList();
return View(model);
}
// create and persist a new dog
return RedirectToAction("Index");
}

Make a complex typed property required in an MVC4 form

I can't figure out how to "customize" the rules for the [Required] attribute when I stick it to a custom typed property. Code looks like this:
public class MyProp
{
public Guid Id {get;set;}
public string Target {get;set;}
}
public class MyType : IValidatableObject
{
public string Name {get;set;}
public MyProp Value {get;set;}
private MyType()
{
this.Name = string.Empty;
this.Value = new MyProp { Id = Guid.Empty, Target = string.Empty };
}
public MyType(Guid id) : this()
{
this.Value.Id = id;
// Fill rest of data through magic
}
public IEnumerable<ValidationResult> Validate(ValidationContext validationContext)
{
if(this.Value.Id == Guid.Empty)
yield return new ValidationResult("You must fill the property");
}
}
This model shows up in forms (through its own EditorTemplate) as a textbox with a button which allows for selection from a list (the backing data is a Dynamics CRM 2011 Environment, and this model is actually aimed to represent a lookup attribute).
public class MyModel
{
// Many props
[Required] // This one is enforced correctly
public string MyString {get;set;}
[Required] // This one isn't
public MyType MyData {get;set;}
public MyModel() { this.MyData = new MyType(); }
}
The resulting view shows the field (empty, of course). User can only input data by clicking the field and choosing from a list (a jquery dialog takes care of this, and it already works).
The IValidatableObject interface sounds promising but the code doesn't seem to be ever invoked.
In the controller, I'm simply doing
[HttpPost]
public ActionResult MyAction(FormCollection data)
{
if (!ModelState.IsValid) return View();
// magic: handle data
}
What am I missing ? I probably misunderstood the IValidatableObject interface usage ?
Your controller action should take the view model as parameter instead of weakly typed FormCollection which has absolutely no relation to your model (and its validation rules):
[HttpPost]
public ActionResult MyAction(MyModel model)
{
if (!ModelState.IsValid)
{
return View();
}
// magic: handle model
}
Now the default model binder is going to be invoked in order to bind the view model from the request and evaluate any validation logic you might have in this model.
How do you expect from your code, ASP.NET MVC, to ever know that you are working with this MyModel class? You absolutely never used it in your POST action, so you cannot expect to have any validation on it.
Once you start using view models you should forget about weakly typed collections such as FormCollection and start working with those view models.

ActionResult cast parameter base class to derived class

I've written a base class and some classes which derive from it.
I want to use these classes in one ActionResult, but if I'm trying to cast PSBase to PS1 I'm getting a System.InvalidCastException that type PSBase can not be converted to PS1.
Classes:
public class PSBase
{
public int stationId { get; set; }
public string name { get; set; }
}
public class PS1 : PSBase
{
public string reference { get; set; }
}
public class PS2 : PSBase
{
}
ActionResult:
[HttpPost]
public ActionResult ProductionStep(PSBase ps)
{
if (ModelState.IsValid)
{
var product = db.Product.FirstOrDefault(.........);
switch (ps.stationId )
{
case 1:
{
product.Reference = ((PS1)ps).reference;
db.SaveChanges();
break;
}
}
}
return View();
}
As I don't want to have for each class a own ActionResult (repeating much of the same code many times) I wanted put all this to one ActionResult. Any Ideas how I could implement this?
What you are trying to do will never work without custom ModelBinder (and even then it will be a huge mess I'd not recommend to implement), sorry.
Only when you are passing a model from Controller to View it remembers what type it was originally (including inheritance, etc.) because at that point you are still on the server side of the page and you are merely passing an object.
Once you enter a view and submit a form all that does it creates some POST request with body containing list of values based on input names.
In your case if you have a form based on PS1 and used all the fields as inputs, you would get something like:
POST:
stationId = some value
name = some value
reference = some value
(there is no mention of the original type, controller, method, etc.)
Now, what MVC does is that it checks what argument you are using in the header of the method (in your case ProductionStep(PSBase ps)).
Based on the argument it calls a model binder. What the default model binder does is that it creates new instance of that class (in your case PSBase) and goes via reflection through all the properties of that class and tries to get them from the POST body.
If there are some extra values in the POST body those are forgotten.
Unless you write a custom model binder for this default MVC implementation can't help you there.
I'd recommend creating two separate methods, one of each accepting different implementation of PSBase.
If you want to read more on Model Binders check this out http://msdn.microsoft.com/en-us/magazine/hh781022.aspx
EDIT:
By creating two separate methods I mean something like this:
[HttpPost]
public ActionResult ProductionStepA(PS1 ps)
{
if (ModelState.IsValid)
{
}
return View();
}
[HttpPost]
public ActionResult ProductionStepB(PS2 ps)
{
if (ModelState.IsValid)
{
}
return View();
}
then you have to distinguish between them in the view via different form action.

ASP.NET MVC - Trouble passing model in Html.ActionLink routeValues

My View looks like this:
<%# Control Language="C#"
Inherits="System.Web.Mvc.ViewUserControl<TMS.MVC.BusinessSystemsSupport.Models.SearchDataTypeModel>" %>
<table class="classQueryResultsTable">
<!-- the header -->
<tr class="headerRow">
<td>
<%= Html.ActionLink("Effective Startdate",
"SortDetails",
"DataQryUpdate",
new
{
model = Model,
sortBy = "EffectiveStartDate",
},
new { #class = "classLinkLogDetails" })%>
</td>
</tr>
</table>
My controller action:
public ActionResult SortDetails(SearchDataTypeModel model, String sortBy)
{
The model parameter is null. The sortBy parameter is populated. I can pass in a String property from the model to the action with no problem. I want to pass in the entire model though.
Any ideas what I'm doing wrong?
You can't pass complex objects:
new
{
model = Model,
sortBy = "EffectiveStartDate",
},
model = Model makes no sense and cannot be sent using GET. You might need to use a form with an editor template and/or hidden fields to send all the model properties. Remember only scalar values can be sent in the query string (key1=value1&key2=value2...). Another alternative that comes to mind is to send only the ID:
new
{
modelId = Model.Id,
sortBy = "EffectiveStartDate",
},
and in your controller action fetch the model given this id from your data store:
public ActionResult SortDetails(int modelId, String sortBy)
{
var model = repository.GetModel(modelId);
...
}
Of course this is only true if the user is not supposed to edit the model properties in a form. Depends on your scenario.
And for the sake of completeness let me expose another option: use the Html.Serialize helper from MVC Futures to serialize the entire model into a hidden field which could be passed back to the controller action and deserialized there.
There is another way of passing model or complex objects specifically in ActionLink as RouteValues.
MODEL: Make static Serialize and Deserialize methods in the class like
public class XYZ
{
// Some Fields
public string X { get; set; }
public string Y { get; set; }
public string X { get; set; }
// This will convert the passed XYZ object to JSON string
public static string Serialize(XYZ xyz)
{
var serializer = new JavaScriptSerializer();
return serializer.Serialize(xyz);
}
// This will convert the passed JSON string back to XYZ object
public static XYZ Deserialize(string data)
{
var serializer = new JavaScriptSerializer();
return serializer.Deserialize<XYZ>(data);
}
}
VIEW: Now convert your complex object to JSON string before passing it in Action View
<%= Html.ActionLink(Model.x, "SomeAction", new { modelString = XYZ.Serialize(Model) })%>
CONTROLLER: Get the object as string in Action method and convert it back to object before using
public ActionResult SomeAction(string modelString)
{
XYX xyz = XYX.Deserialize(modelString);
}
Thats All...
Note: Techniques discussed in other answers are well enough in case of Model, But some times you need to pass some complex object (other than database model) back to the controller, as I have such specific case.
Hope this will help some...:)
One other option is to persist the data you need in TempData. This will hand it to the next request, and you can retrieve it there. You should be able to persist the entire model object if you want to.
But it's easier (and better practice) to just retrieve it again from the database, as Darin suggests.
You would have to serialize the object. The url would get ugly and risk becoming to long.
This is kind of close to what you was looking for.
I use custom parameters that persist only within a controller.
Just easy to maintain and they are strong typed. I don't like variable in quotes.
If I use these in a form submit then all is good.
<form><%=Html.TextBox("catid.Value") %></form>
If I use Html.ActionLink then it no worky.
Basically the Url has to look something like this
?catid.Value=31f1a21a-9546-4f2f-8c26-a0273d11b233
The work around is pretty simple since I still remember how to manually write an html A tag.
<a href="?catid.Value=<%= cat.ID %>" ><%: cat.Name %></a>
public ActionResult Index(Core.ControllerPersistence._Guid catid)
{
if (catid.Value.HasValue)
{
Not all but some of the Html helpers are like a pen you would carry in your pocket that automatically signs your name so you don't have to move your wrist. If for some reason the pen one day does not work just grab a normal pen and move your wrist so you can sign your name and move on.
Jeff,
Maybe you could create a View class that has the properties SearchDataTypeModel and sortby and you pass it to the view. When you click the actionlink pass it just the Model.SearchDataTypeModel. HTH
Maybe it is too late. Got some solution. Something similar to this.
Here is my example.
Url generating code:
var rv = new RouteValueDictionary();
rv["sortBy"] = currentSortColumn;
rv["ascending"] = currentSortColumn == sortBy ? !ascending : true;
rv["filter.Id"] = // some value
rv["filter.Creator"] = // some value
var url = url.Action( // url is UrlHelper
actionName,
controllerName,
rv);
// as result it will output something like this:
// http://your_host/yourController/yourAction?sortBy=name&ascending=True&filter.Id=100&filter.Creator=test
Controller code:
public ActionResult YourAction(string sortBy = "name", bool ascending = false, YourFilterModel filter = null)
Filter object class:
public class YourFilterModel
{
public string Id { get; set; }
public string Creator { get; set; }
}

Categories