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

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

Related

Build a Layout ViewModel based in a Id from the URL with MVC

I need to build a ViewModel for my Layout's Web Application, I have tried this solution but it's not based on a URL's Id coming from the URL to generate the Layout's ViewModel.
I tried this but I first had the not existing empty controller error, then I tried to include the id as a parameter but I get the error "Object reference not set to an instance of an object." because the LayoutColorRGB is not set.
public MobileController(int id)
{
Event model = db.Events.Where(s => s.Id == id).FirstOrDefault();
LayoutVM = new LayoutVM()
{
EventId = model.Id,
LayoutColorRGB = model.LayoutColorRGB,
SponsorLogoLink = model.SponsorLogoLink,
SponsorLogoURL = model.SponsorLogoURL
};
ViewData["LayoutVM"] = LayoutVM;
}
There are many cases need to extract data based on the request context and show something on layout pages. For example:
You may want to show logged-in user info
You may want to show number of visitors or online visitors
You you may want to show the current language and let the user to change the language.
You may want to load site menu or side-bar or footer from database
To do so you can consider the following points:
Partial View: You can create some small partial views for those parts having a specific model for each partial view and render them in the layout page.
Use context to get data: You can initialize the model by extracting information from Request, ViewContext, RouteData, ValueProvider and other context objects.
Access to data by HTML Helpers: You can create a HtmlHelper to get data from context and use the helper in the layout or partial views.
Access to data by dependency injection: You can define some services for extracting data and then inject those data to layout pages. In the service, you will initialize the model using context objects. If you are using ASP.NET CORE, this is a good way to go.
Access to data as property of base controller: You can have a property in the base controller and initialize it in constructor of the controller or in OnActionExecuting. Then in the layout, get the property by casting ViewContext.Controller to type of your base controller and read the property.
Access to data by ViewBag: You can initialize an instance of the model in constructor of a base controller or in OnActionExecuting method of the base controller and then put it in ViewBag. Then you can easily use it in view.
Layout Pages: Don't forget you can define different layout pages and use different layouts based on your requirement. You can set the layout in the action or in _ViewStart.
Example
Trying to resolve id in each request, means you need to have id as part of all requests or you need to know what should you do in absence of id. Considering this fact and to keep things simple for a minimal example, I'll define the following model and base controller and try to resolve id in OnActionExecuting method of the base controller and then will derive all my controllers which needs such behavior from this base controller.
You can do the same using an ActionFilter or a global action filter of the OnActionExecuting method of your controller.
Using the following code:
If you browse /home/index you will see a red bar at bottom of the page.
If you browse /home/index/1 you will see a blue bar at the bottom of the page
If you browse /home/index/2 you will see a green bar at the bottom of the page
Layout Model
public class LayoutViewModel
{
public int? Id { get; set; }
public string Color { get; set; }
}
Base Controller
public class BaseControllr : Controller
{
protected override void OnActionExecuting(ActionExecutingContext filterContext)
{
//Get the id from route
var id = int.TryParse(ValueProvider.GetValue("id")?.AttemptedValue, out var temp)
? temp : default(int?);
var model = new LayoutViewModel();
//Your logic to initialize the model, for example
model.Id = id;
if (model.Id == null)
model.Color = "FF0000";
else if (model.Id%2==0)
model.Color = "00FF00";
else
model.Color = "0000FF";
//Set ViewBag
ViewBag.MainLayoutViewModel = model;
base.OnActionExecuting(filterContext);
}
}
Home Controller
public class HomeController : BaseControllr
{
public ActionResult Index(int? id)
{
return View();
}
}
_Layout.cshtml
Then in the _Layout.cshtml, add the following code before closing <body/> tag for test:
#{
string color = ViewBag.MainLayoutViewModel?.Color;
int? id = ViewBag.MainLayoutViewModel?.Id;
}
<div style="background-color:##color;">
Id:#id
</div>
You should have a default value for each property of your layout. Then if your model doe not have some property you can use it from the default layout.
internal static readonly LayoutVM defaultLayout = new LayoutVM()
{
EventId = 0,
LayoutColorRGB = "#FFFFFF",
SponsorLogoLink = "AnyLink",
SponsorLogoURL = "AnyImageUrl"
};
public MobileController(int id)
{
Event model = db.Events.Where(s => s.Id == id).FirstOrDefault();
if (model == null)
{
ViewData["LayoutVM"] = defaultLayout;
return;
}
LayoutVM = new LayoutVM()
{
EventId = model.Id,
LayoutColorRGB = model.LayoutColorRGB ?? defaultLayout.LayoutColorRGB,
SponsorLogoLink = model.SponsorLogoLink ?? defaultLayout.SponsorLogoLink,
SponsorLogoURL = model.SponsorLogoURL ?? defaultLayout.SponsorLogoURL
};
ViewData["LayoutVM"] = LayoutVM;
}

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)

Model Binding: Safe cast returns null [duplicate]

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.

MVC Pass display data between views

I have a view model that is used to display a form on one view, and then is also used to represent the POST data to an action. The action then displays another view model that contains much of the same data from the first view model. However, the first view model has several "display only" properties that are also required on the second view model (for display only on the second view also).
I am wondering what the best way to pass this "display only" data to the second view would be. Currently, the best solution I have come up with is to have a bunch of hidden form fields that contain the display only property values, and then the model gets auto-populated for the action that handles the form POST. However, using hidden form fields seems very "hackish", and there seems like there should be a better solution to passing this data to another view The action doesn't need the display only information, it is only accessing it to populate the properties of the second view model that is passed to the second view.
Let me just explain my question with code, as what I am after is probably better understood through code than words.
Models:
public class SearchFilters
{
// ...
}
public class SearchResult
{
public int Id { get; set; }
public bool Selected { get; set; }
public string SomeDisplayValue1 { get; set; }
public string SomeDisplayValue2 { get; set; }
// ...
}
public class ResultsViewModel
{
public IList<SearchResult> Results { get; set; }
// ...
}
public class DoSomethingWithSelectedResultsViewModel
{
public IList<SearchResult> SelectedResults { get; set; }
public string SomeOtherProperty { get; set; }
// ...
}
Controller:
[HttpPost]
public ActionResult Results(SearchFilters filters)
{
ResultsViewModel results = new ResultsViewModel();
// ...
return new View(results);
}
[HttpPost]
public ActionResult DoSomethingWithSelectedResults(ResultsViewModel model)
{
// ...
return View(new DoSomethingWithSelectedResultsViewModel
{
SelectedResults = model.Results.Where(r => r.Selected).ToList(),
SomeOtherProperty = "...",
// ...
});
}
View: Results.cshtml
#model ResultsViewModel
#using (Html.BeginForm("DoSomethingWithSelectedResults", "Search"))
{
<table>
for (int i = 0; i < Model.Results.Count; i++)
{
<tr>
<td>
#Html.CheckBoxFor(m => Model.Results[i].Selected)
#* I would like to eliminate these hidden inputs *#
#Html.HiddenFor(m => Model.Results[i].Id)
#Html.HiddenFor(m => Model.Results[i].SomeDisplayValue1)
#Html.HiddenFor(m => Model.Results[i].SomeDisplayValue2)
</td>
<td>#Html.DisplayFor(m => Model.Results[i].SomeDisplayValue1)</td>
<td>#Html.DisplayFor(m => Model.Results[i].SomeDisplayValue2)</td>
<tr>
}
</table>
<button type="submit">Do Something With Selected Results</button>
}
As far as I know, one of the best way to pass data from View to another View through a Controller is to use ViewBag, ViewData or TempData. As an example, you can pass the data retrieved from View I as shown below:
TempData[DataToBePassed] = model.CustomData;
And then retrieve this data in View II similar to that:
#if(TempData[DataToBePassed] != null)
{
var dataFromFirstView = TempData[DataToBePassed];
}
For more information take a look at When to use ViewBag, ViewData, or TempData in ASP.NET MVC 3 applications.
You could put the model in the TempData property of the controller, that way it's automatically available in the next request.
More here
Found what I was looking for, I just hadn't worked with MVC enough yet to know about it. The Controller.UpdateModel method does exactly what I was looking for.
Example (using the code from the question):
[HttpPost]
public ActionResult DoSomethingWithSelectedResults()
{
// Load initial model data here, in this case I had simply cached the results in
// temp data in the previous action as suggested by Emeka Awagu.
ResultsViewModel model = (ResultsViewModel)TempData["results"];
// Call UpdateModel and let it do it's magic.
UpdateModel(model);
// ...
return View(new DoSomethingWithSelectedResultsViewModel
{
SelectedResults = model.Results.Where(r => r.Selected).ToList(),
SomeOtherProperty = "...",
// ...
});
}
Using this method I was able to eliminate all the hidden form fields and did not have to write any custom copy logic, since UpdateModel deals with it automatically.
Note: I did have to implement some custom model binders to get things to work correctly with dictionaries and collections (see here, here, and here).

Passing data from a View to a Controller alongside a Form's values

Part of the website I'm doing (a simple turn-per-turn browser game) requires me to pass data from a View to a controller (passing an Foreign Key from a DropDownList and another integer value in the view as argument to the controller). Next the controller uses these values to pass execution to another controller.
My problem is that I have troubles figuring out how MVC returns the data inside my view and bundle inside types and use them as parameters. I know I could use an action link to point to my next controller but I do not know how to retrieve the selected value in the DropDownList. That or use an Input Button but then I do not know what or how the values are returned.
Do tell me if you require extra explanation or anything is confusing.
Here is my code :
The view in Razor, receiving a view model, defined below.
#model BattleForLurnia_MVC.Controllers.ChooseCharacterViewModel
#{
ViewBag.Title = "ChooseCharacter";
}
<h2>Choose Your Character</h2>
<fieldset>
<legend>Choose your fighter !</legend>
<div class="display-label">Your characters :</div>
<div class="display-field">
#Html.DropDownList("CharactersList", String.Empty)
</div>
#* Here is the part that returns the values, either an ActionLink or
an Input button.*#
</fieldset>
My ViewModel passed to the View
public class ChooseCharacterViewModel
{
Battle_For_LurniaEntities db = new Battle_For_LurniaEntities();
public Player player { get; set; }
public Character targetCharacter { get; set; }
public ChooseCharacterViewModel(string playerUsername, Character opponent)
{
player = db.Players.Single(plr => plr.Username == playerUsername);
targetCharacter = opponent;
}
public ChooseCharacterViewModel(){}
}
The controler that sends the View
public ActionResult ChooseCharacter(int targetCharacter_ID)
{
Character targetChar = db.Characters.Single(chr => chr.ID_Character == targetCharacter_ID);
ChooseCharacterViewModel chooseCharacter = new ChooseCharacterViewModel(Session["Username"].ToString(), targetChar);
// Using LINQ to get characters owned by the current player. Fuckyeah.jpg =D
IEnumerable<Character> blabla = from VAR in db.Characters
where VAR.FK_Player_ID == chooseCharacter.player.ID_Player
select VAR;
ViewBag.CharactersList = new SelectList(blabla, "ID_Character", "Name");
return View(chooseCharacter);
}
And finally the controller that receives the data in my view and sends another view.
[HttpPost]
public ActionResult ChooseCharacter(int FK_Character_ID, Character targetCharacter)
{
Character sourceCharacter = db.Characters.Single(chr => chr.ID_Character == FK_Character_ID);
return RedirectToAction("Fight", new {SourceCharacter = sourceCharacter, TargetCharacter = targetCharacter});
}
In your ViewModel, create three properties
SelectedId
ListOfItems
OtherItemsThatYouNeedToGetBackAtController
When you POST back to your controller using a Html.BeginForm, the model will be passed back. For getting the Selected DropDown Value, change the syntax as below:
#Html.DropDownListFor(m=>m.SelectedId, new SelectList(Model.ListofItems))
For the OtherItemsThatYouNeedToGetBackAtController, You can use
#Html.HiddenFor(m=>m.OtherItemsThatYouNeedToGetBackAtController)

Categories