Inheritance in action method parameters - c#

I have the following classes:
class SomethingBase
{
public string SharedProperty { get; set; }
}
class ChildClassOne : SomethingBase
{
public string SpecificPropertyOne { get; set; }
}
class ChildClassTwo : SomethingBase
{
public string SpecificPropertyTwo { get; set; }
}
And I have ASP.NET MVC View which has two HTML-forms. These forms are calling the same action method.
This action method should receive any of two SomethingBase class derivatives.
However, if I create single parameter like SomethingBase param, then only the SharedProperty is received. This behavior can be explained by binding mechanism of ASP.NET MVC.
To make my action method work I created the next definition:
public ActionResult(ChildClassOne param1, ChildClassTwo param2)
SharedProperty goes to both params, but specific properties are populated only for object, which was actually passed from view. It works, but I don't think that this is the only solution.
Are there some best-practice solutions for this situation?

You should create a view model for each action since they are not alike. There's really no reason to try to use a base class in this case.

Method TryUpdateModel of Controller class make it work. However, this way is not pretty elegant.
...
public ActionResult Save(FormCollection collection)
{
SomethingBase model = null;
if (collection.AllKeys.Contains("SpecificOne"))
{
model = new ChildOne();
TryUpdateModel<ChildOne>((ChildOne)model, collection);
}
else
{
model = new ChildTwo();
TryUpdateModel<ChildTwo>((ChildTwo)model, collection);
}
...

Related

Simple way to bind abstract class model, ASP.Net Core 5

In short, I have a model class which inside of it has some properties as well as some properties which types are abstract classes.
The model looks something like this:
public class ModelClass
{
public string SomeProp { get; set; }
public AbstractOne FirstAbstract { get; set; }
public AbstractTwo SecondAbstract { get; set; }
}
And the abstract classes:
public abstract class AbstractOne
{
public virtual Type TwoType { get; set; }
public string SomeProp { get; set; }
public string SomeOtherProp { get; set; }
}
public abstract class AbstractTwo
{
// Its empty
}
So in the actual code, I have a class that inherits each one of those abstract classes, and in the controller I initially give the model as:
return View(new ModelClass()
{
SomeProp = "Somevalue",
AbstractOne = valueUpInTheCode
AbstractTwo = (AbstractTwo)Activator.CreateInstance(valueUpInTheCode.TwoType)
}
Which works fine, the View gets the model, and I can use the html helpers to generate form fields.
I have a form which has a Html.HiddenFor for SomeProp and AbstractOne, DisplayFor for AbstractOne and EditorFor AbstractTwo which is specific to the class that inherits the AbstractTwo, so the EditorFor line looks something like this:
#Html.EditorFor(model => model.SecondAbstract, Model.SecondAbstract.GetType().Name)
Which, on the UI seems fine at least, all of the default values or values passed when giving creating model for the view appear and seem to be there.
The problem is when that form is submitted, I have a function that should handle it, for now its empty, but I'm still debugging so I just need some place to put a breakpoint:
[HttpPost]
[ValidateAntiForgeryToken]
public IActionResult Submit(ModelClass model)
{
if (ModelState.IsValid == false) return View("Index", model);
return View("Index", model);
}
The problem is, that model ends up having all of its properties set how they were entered, but the FirstAbstract and SecondAbstract don't seem to properly bind and end up both being null.
So far I've tried some stuff I found on here which seemed to be either outdated or not working for my use case, the closest I got to something working was with this blog on how to make a custom Model Binder:
https://www.palmmedia.de/Blog/2018/5/13/aspnet-core-model-binding-of-abstract-classes
It didn't end up working in the end, but gave me an idea of how something like this should be fixed... And I've got no clue how to do that, or where to start even.

Class type object as HttpGet

I am trying to create an action that would look like controller/action?param1=val&param2=val with the HttpGet annotation.
What I have is:
[HttpGet]
public IActionResult Index(SomeClass obj)
{
// do stuff
return View(something);
}
I can access the action via controller/Index?obj.param1=val&obj.param2=val, but is there a way to avoid obj.param1 and obj.param2 in the query string and have something like controller/Index?page=val&amount=val.
Putting those parameters in the annotation like this didn't work: [HttpGet("/page={obj.subobject.param1}&amount={obj.subobject.param2}")]
Assuming the default model binding setup, you can just pass the parameter names directly and ASP.NET Core will automatically put the values into the SomeClass object:
public IActionResult Test(SomeClass obj)
{
return Json(obj);
}
public class SomeClass
{
public string Foo { get; set; }
public string Bar { get; set; }
}
When opening the URL /Home/Test?foo=baz&bar=qux you will now see that the object is properly filled with the Foo and Bar properties.

Calling the controller method depending on the incoming parameters

I want to implement a certain functionality, but I do not know where to start. I will describe what I have.
Backend
public enum SourceType { Database, Folder }
public class DatabaseSource
{
public string ServerName { get; set; }
public string DatabaseName { get; set; }
}
public class FolderSource
{
public string FolderName { get; set; }
}
public class TestController : ApiController
{
[HttpPost]
[Route("source")]
public void Post([FromBody]DatabaseSource source) //method one
{
}
[HttpPost]
[Route("source")]
public void Post([FromBody]FolderSource source) //method two
{
}
}
Frontend
export enum SourceType {
Database,
Folder
}
export class DatabaseSource {
public ServerName: string;
public DatabaseName: string;
}
export class FolderSource {
public FolderName: string;
}
var source = new DatabaseSource();
source.ServerName = "serverName";
source.DatabaseName = "dbName";
var obj = {
sourceType: SourceType.Database,
source: source
};
Now imagine that I will send obj to the server. I want that specific controller method to be called depending on the enum. How can I do this?
P.S. The example is greatly simplified.
Your implementation is inconsistent for what you've specified in code.
On the front-end you are describing an object which has a sourceType field and a source object property, while on the backend you're overloading the ApiController method and mapping different REST object resources to a single HTTP method and endpoint (which I believe will not work).
There is no magic way for the ApiController to use your enum property to differentiate between the object types automatically.
A simpler (and better) implementation would be to have separate ApiController classes for your Database and Folder source object POST calls. This follows the principle of REST API design where you are essentially mapping basic CRUD operations to the HTTP methods with object types.
If your intention is to perform an operation based on these parameter objects, then clarify the intention via the API routing for the endpoint as below:
public class TestController : ApiController
{
[HttpPost]
[Route("ETLLoad/Database/source")]
public void Post([FromBody]DatabaseSource source) //method one
{
}
[HttpPost]
[Route("ETLLoad/Folder/source")]
public void Post([FromBody]FolderSource source) //method two
{
}
}

MVC - how to send to the partial view ViewModel containing a list

I am the beginner in MVC and I have a web application, where in my controller I declare a list of objects (feedback from visitors) and then send it to the view, which displays it. It looks like this. Declaration:
public class TrekFeedbackItem
{
public string trekid { get; set; }
public string comment { get; set; }
public string author { get; set; }
public TrekFeedbackItem(string trekid, string comment, string author)
{ this.trekid = trekid;
this.comment = comment;
this.author = author;
}
}
And usage:
List<TrekFeedbackItem> feedbackList = new List<TrekFeedbackItem>
{
//constructor called, data entered into the list
}
return View(trekname, feedbackList);
However, now I need to pass also another list, lets call it relatedblogsList. As a first step, I decided to encapsulate my feedbackList into the ViewModel (and once it works, add another list of different objects.)
public class TrekViewModel
{
public List<TrekFeedbackItem> feedback { get; set; }
}
and fill the data like this:
TrekViewModel trek = new TrekViewModel();
trek.feedback = new List<TrekFeedbackItem>
{
//insert data here
};
return View(view, trek);
The problem is - how to send this model to the partial view and how to access it?
Thank a lot
You can pass data into the partial view like below
from the controller return this view:
return PartialView("_partial_viewname", trek);
then in the beginning of the partial view:
#model Models.TrekViewModel
after that you can use Model.feedback inside the partial view.
Set return type of your action controller to "PartialView" rather than "View".
return PartialView("_yourPartialViewName", yourObject);
In case, if application does not work as expected, build it and re-run it.

MVC Model Class not populated on post

I have two MVC models that look like this:
public class OtherModel
{
[Required]
[Display(Name = "Another ID")]
public int id{ get; set; }
}
public class MyModel
{
[Required]
[Display(Name = "ID")]
public int id { get; set; }
public PlayerModel otherModel = new OtherModel ();
}
My controller has an [HttpPost] action called USE that looks like this:
[HttpPost]
public ActionResult Use(MyModel myModel)
{
/// myModel.otherModel.id is 0 here!!
}
This action takes in a MyModel. When my form is being posted, the otherModel variable contains a 0 for the id value. Now, the view that contains the form is handed a MyModel and actually displays the otherModel.id on the page. The problem is the post action is not
properly marshalling the form data into the otherModel object and I have no clue why.
Another note: When I examine the form data headers for the post, I clearly see otherModel.id with the value that I expect.
Why is this data not appearing correctly within my otherModel object?
Thank You in advance!
Did you registered binder in Global.asax.cs?
public static void RegisterBinders(ModelBinderDictionary binders)
{
binders.Add(typeof(MyModel), new MyModelBinder());
// other binders
}
This is called in Application_Start like the following:
protected void Application_Start()
{
RegisterBinders(ModelBinders.Binders);
}
PS: I assumed you are using a custom model binder. In case you are using automatic binding see if you respect the naming conventions.
Instead of initializing otherModel with a new object at the line PlayerModel otherModel = new OtherModel();, use a property public PlayerModel otherModel { get; set; }. otherModel needs a property setter for the model binder to assign the value properly. This may require you to also change how you populate the otherModel property when displaying the view - construct an object and assign it explicitly in either the displaying controller method or some other function that hydrates the model.
I had an almost identical issue. The fix for me was as Matt mentioned, to make the inner object a property with the needed accessors.
public class OuterModel
{
public OuterModel ()
{
AuxData= new InnerModel();
}
public InnerModel AuxData{ get; set; }
}
public class InnerModel
{
Int Id {get; set;}
}

Categories