Class type object as HttpGet - c#

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.

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.

ASP.NET Core querystring object

I have a method called in get that receive in input (from the query string) some parameters that represent an object:
public override async Task<IActionResult> Index([FromQuery]MyFilter filter)
{
...
}
public class MyFilter : BaseFilter
{
public List<string> Rules{ get; set; } = new List<string>();
public string SearchString { get; set; }
}
If I call my method with this query string the property Rules is empty:
?filter.SearchString=&filter.Rules[]=foo&filter.Rules[]=bar
but if I call it with this query string, the property Rules is filled correctly:
?filter.SearchString=&filter.Rules[0]=foo&filter.Rules[1]=bar
So the field "Role"s is a html select so when user fill this field I do not know indexes.
Does someone have the same problem (and a solution for it)?
I think its better to use:
?SearchString=&Rules[]=foo&Rules[]=bar

MVC viewModel and paged results

I have a need to pass into a controller a viewmodel as per below.
[HttpPost]
public JsonResult GetSearchResultsJson(SearchCriteria searchCriteria)
{
}
SearchCriteria is defined as:
public class SearchCriteria
{
public SearchData searchData { get; set; }
public SearchMode searchMode { get; set; }
}
Where SearchMode is:
public class Searchmode
{
public int? mode { get; set; }
public int? pageNumber { get; set; }
public int? pageSize { get; set; }
}
And SearchData has 61 properties that define what items are to search for.
public class SearchData
{
public string name {get;set;}
....
public int age {get;set;}
}
I populate an object using jQuery and pass that to the controller. .Net converts this object into an object of type SearchCriteria. All is working, but when the PagedListPager control is rendered, how do i emulate the jQuery used to create the object?
At the moment I have the following code:
#Html.PagedListPager(Model.DocumentsPaged, pageNumber => Url.Action("GetSearchResultsJson", XXXXXXXXXXXXXXXXXXXXXXX),pLRO)
And do not know what to put in the bit marked as XXXXXXXXXXXXXXXXXXXXXXX.
Within jQuery, I can modify the pageNumber property of the SerachMode object and this does provide me with the correct page, but it is precisely this property that I need to update within the Html.PagedListPager helper.
As described in example here you can pass the page.
I really suggest you to clone the example code and play a bit with it.
Probably you have to add your search parameters as well, in case you lose them server-side.
Your method GetSearchResultsJson(SearchCriteria searchCriteria) is marked [HttpPost], so you can leave the URL parameters blank and just use Url.Action("GetSearchResultsJson"). The search parameters for a POST go in the body of the request instead of the URL.
Because the Html.PagedListPager method expects a 'page' parameter (assuming you are using the method from the PagedList.Mvc NuGet package), you may want to write your search function like this:
GetSearchResultsJson(int page, int SearchCriteria searchCriteria)

Inheritance in action method parameters

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);
}
...

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