I'm developping an application based on asp.NET MVC 5
This Application downloads a big deal of data from a local webservice and should display it.
Because it is much data, I call the webservice through a partial view, like this:
<div class="partialContents" id="partialContent" data-url="/Home/Action">
Loading...
</div>
This is loaded asynchronous with the following JS-Code:
$(document).ready(function (e) {
$(".partialContents").each(function (index, item) {
var url = $(item).data("url");
if (url && url.length > 0) {
$(item).load(url);
}
});
});
This works like intended.
But now I have to pass the values of a form to this partial view.
I know I can pass arguments as usual, like a request to /Home/Action/Params but I have to pass many arguments of which some might not be set or empty or null.
Because of this, I looked for a possibility to pass a object or a value of the ViewBag or something like this to the partial view.
Is there a possibility to pass a Model-Object to the Controller in my code?
The data has to go to the controller for further validation and so on. I know I can pass a model to the view itself, but I need those data in my controller.
My controller is accessed as following:
public ActionResult Index()
{
//Do things
return View(model);
}
public ActionResult Action()
{
//Validate the Form-Data
//Download Data from Webservice
return PartialView("MyPartialView", model);
}
Any help or tipps would be greatly appreciated.
Maybe I have to edit the Javascript-Code, but I don't usually code in JS, so I have the code from following source (followed his tutorial): blog.michaelckennedy.net
//Additional Info
My ViewModel looks like this:
public class PostForm
{
public string DatumVon { get; set; }
public string DatumBis { get; set; }
public string Werk { get; set; }
public string Pruefplatz { get; set; }
public string Auftrag { get; set; }
public string Gueltig { get; set; }
}
and my Java-Request like this:
$(item).load(url, {"DatumVon":dateVon, "DatumBis":dateBis, "Werk":werk, "Pruefplatz":pruefplatz, "Auftrag":auftrag, "Gueltig":gueltig});
If you are having a lot of parameters to pass to the action method, create a model which encompasses these parameters. For example, I have created MyParamModel class here below:
public class MyParamModel
{
//Your parameters go here
public int IntProperty { get; set; }
public string StringProperty { get; set; }
}
Then modify the action method to accept this class as parameter.
public ActionResult Action(MyParamModel model)
Modify the load call in the script to pass information to the url like this.
$(item).load(url, {"IntProperty":1, "StringProperty": "test"});
Once you have the model on the controller end, you can validate it accordingly.
Hope this helps. If it does, mark this as answer.
Related
My client wants Export to CSV functionality on a search results page. I am running a ASP.NET MVC application and we are using Razor for our frontend. I think I understand how referring to the #model works, but I am unable to actually pass the data passed to the view back to a controller to use with a different action. For example, I have a list of search results defined as the model's type:
#model IList<Capstone_Museum.Models.SearchResult>
SearchResult.cs:
public class SearchResult {
[JsonPropertyName("guid")]
public string Guid { get; set; }
[JsonPropertyName("scientific_name")]
public string ScientificName { get; set; }
[JsonPropertyName("verbatim_date")]
public string Date { get; set; }
[JsonPropertyName("country")]
public string Country { get; set; }
[JsonPropertyName("state_prov")]
public string State { get; set; }
[JsonPropertyName("spec_locality")]
public string Locality { get; set; }
[JsonPropertyName("dec_lat")]
public decimal? Latitude { get; set; }
[JsonPropertyName("dec_long")]
public decimal? Longitude { get; set; }
[JsonPropertyName("coordinateuncertaintyinmeters")]
public int? UncertaintyMeters { get; set; }
}
If I pass the Model back to a controller's action, the list comes back null. I do it like this:
#Html.ActionLink(linkText: "Export to CSV", actionName: "ExportResults", controllerName: "SearchResults", routeValues: Model)
and I'm receiving the list in my action like this:
public IActionResult ExportResultsCSV(List<SearchResult> searchResults) {
return File(new UTF8Encoding().GetBytes(SearchResult.ListToString(searchResults)), "text/csv", "ExportResults.csv");
}
I've realized that an action passes a string version of whatever data it uses by parameter using the URL. My lists may be thousands of lines long - which is too much to include in a URL, bringing an error.
I just need to download the csv to the client machine. Is there a way to do this, even on the frontend? I'd rather not get the data from the HTML table containing the results to create the CSV.
I think you want to break your actions up into an API action that returns the search results as csv, and a view action that renders the search results to html.
class SearchController {
[HttpGet]
[Produces("text/csv")]
IEnumerable<SearchResult> GetSearchResults(string query){
...
}
[HttpGet]
IActionResult ViewSearchResults(string query)
{
var results = GetSearchResults(query);
return View(results);
}
}
In the view you would link to the GetSearchResults action, which would re-run the query and send the results back to the client as csv... assuming you have a formatter that can produce CSV. By default, in ASP.NET Core only JSON is supported. I maintain a library that provides text/csv formatting that you can use: Sylvan.AspNetCore.Mvc.Formatters.Csv. By adding this nuget package, and registering the formatter, you can use the [Produces("text/csv")] to send results back to the client as CSV instead of JSON.
The only issue you'll need to consider, is that the search results might change between viewing the "view", and then clicking the download as CSV link. It usually isn't an issue, but there might be rare occasion where the html results would disagree with the downloaded CSV.
The question title might be a bit difficult to understand, but I'll try to explain what I am asking here.
I have this sort of controller method in an ASP.NET MVC controller:
public IActionResult DoStuff([FromBody] RequestObject reqObj)
{
// do stuff
ReplyObject repObj = ProcessObject(reqObj);
// do more stuff
return Json(repObj);
}
where RequestObject looks like this:
public class RequestObject
{
public string Field { get; set; }
}
I am calling it from an Angular service like this:
this.http.post("/StuffController/DoStuff", { field: "field value goes here" })
Now this works OK. But now I need DoStuff() to handle different subclasses of RequestObj, for example:
public class RequestObjectA : RequestObject
{
public string FieldA { get; set; }
}
public class RequestObjectB : RequestObject
{
public string FieldB { get; set; }
}
and have DoStuff() somehow figure out whether it was given a RequestObjectA or a RequestObjectB.
Is this kind of thing possible? I have a feeling it is not as the JSON request transmits only the contents of the object, not its class, but I'd like to know for sure.
The short answer is that it is not possible.
The long answer is: your contract for the endpoint parameter is RequestObject class so nor additional parameter members will be respected during deserialization as nothing is known about them.
Got a mismatch somewhere between my View and Controller which is causing the latter to receive a complex object, full of null values.
[HttpGet("find")]
[ProducesResponseType(typeof(PagableResults<UserDetails>), StatusCodes.Status200OK)]
[ProducesResponseType(StatusCodes.Status400BadRequest)]
[SwaggerOperation("FindUsers")]
public async Task<IActionResult> FindUsers([FromQuery]FindUsersSearchFilter searchFilters)
And the searchFilters object is defined like this:
public class FindUsersSearchFilter
{
public int? Page { get; set; }
public string Username { get; set; }
public string Firstname { get; set; }
public string Surname { get; set; }
}
The View is sending the data in a querystring (because it's a get method) like so:
/find?SearchFilters.Page=1&SearchFilters.Firstname=foo&SearchFilters.Surname=bar&SearchFilters.Username=
However, if you debug the controller action the breakpoint is hit but the FindUsersSearchFilter received by the method has a null value for every property.
Things I've tried:
Binding(Prefix="SearchFilters") on the controller action.
Binding("Page,Firstname,Surname,Username") on the controller action
Manually changing the URL to remove the prefix and change the capitalisation
Removing [FromQuery]
At a loss as to where to go next. Any suggestions as to what I've got wrong?
The request is wrong. It should be:
/find?Page=1&Firstname=foo&Surname=bar&Username=
When you prefix all your properties with SearchFilters the binding engine is most likely looking for a nested property like searchFilters.SearchFilters.FirstName.
So removing the prefix should make it work.
If you really need to use that syntax in the query; then create another class like this:
public class SearchFilterContainer
{
public FindUsersSearchFilter SearchFilters { get; set; } = new FindUsersSearchFilter();
}
And pass that in the action as the parameter instead like this:
[HttpGet("find")]
[ProducesResponseType(typeof(PagableResults<UserDetails>), StatusCodes.Status200OK)]
[ProducesResponseType(StatusCodes.Status400BadRequest)]
[SwaggerOperation("FindUsers")]
public async Task<IActionResult> FindUsers([FromQuery]SearchFilterContainer searchFilters)
Then inside your controller you can access the model like this searchFilters.SearchFilters.FirstName
I have two controllers(controllerA.actionA & controllerB.actionB) which share the same view & ViewModel(ViewModel_Shared).
So, in both Controller, I use RedirectToAction to redirect to a third controller(controllerC.actionC) which points to the shared view.
All three Actions are in different Controller.
When ActionA or ActionB was invoked All the parameters that post from view was sent to the modelC in actionC successfully.
However, when I try to feed data into the object(Item), NullReference exception was thrown. the object(Item) was NULL.
But I'm sure the constructor of ViewModel_Shared was hit TWICE while ActionA was called and ActionC was called.
So, basically, object(Item) was NEW twice.
I don't really understand why it's like that.
thank you all in advance!!!
public ActionResult actionA (ViewModel_Shared modelA)
{
return RedirectToAction("actionC", "controllerC", modelA);
}
public ActionResult actionB (ViewModel_Shared modelB)
{
return RedirectToAction("actionC", "controllerC", modelB);
}
public ActionResult actionC (ViewModel_Shared modelC)
{
modelC.FillEditedData();
// Send the data to view
ViewData.Model = modelC;
return View();
}
public class ViewModel_Shared
{
public ItemCustomer Item { get; set; }
public ViewModel_Shared()
{
Item = new ItemCustomer();
}
public void FillEditedData()
{
// NullReference exception was throw here, somehow, Item is null.
Item.LegalCost = "some value";
}
}
[Serializable]
public class ItemCustomer
{
public string Item_No { get; set; }
public string MaterialPricingGroup { get; set; }
public string MaterialGroup { get; set; }
public string Plant { get; set; }
public string LegalCost { get; set; }
}
As mentioned, complex objects cannot travel with requests.
Instead you need to serialize the object and pass the serialized data to the destination action method:
public ActionResult actionA (ViewModel_Shared modelA)
{
var serializedModel = JsonConvert.SerializeObject(modelA);
return RedirectToAction("actionC", "controllerC", serializedModel);
}
UPDATE
My bad. Indeed the parameters, sent through RedirectToAction() are sent as route values so even serialization cannot help here. At least not by itself - the routing must be setup appropriately ...
A different approach must be taken and during actionA, place your modelA into the TempData, then do redirect. In the actionC retrieve the model and carry on.
public ActionResult actionA (ViewModel_Shared modelA)
{
TempData["shared_model"] = modelA;
return RedirectToAction("actionC", "controllerC");
}
Later, in actionC:
public ActionResult actionC (ViewModel_Shared modelA)
{
var modelC = TempData["shared_model"];
modelC.FillEditedData();
ViewBag.Model = modelC;
return View();
}
Do note, that the objects in he TempData are alive only to the next request and then gone. Here you can read more about the TempData.
Yin
1) if you are using the ItemCustomer class as a property you should initialize it when you are using.
public ItemCustomer Item { get; set; }
2) if you are using ItemCustomer as object it will initialize when you constructor call as your example.
public ItemCustomer Item;
I have run the second point without get and set it's working fine.
I'm trying to add a form to allow users to comment on posts on my blogging application. So far, I've added a form to the post details view and I can submit comments, adding them to my database correctly. However, I have a problem with displaying validation errors to the user. The comment form is contained within a partial view and is rendered using Html.RenderAction inside the post details view. I'd like to stress that I don't want to use AJAX for this as I'd like to approach this from a progressive enhancement point-of-view.
Here's the relevant posting action:
[HttpPost, Authorize]
public ActionResult AddComment(CommentViewModel newComment)
{
if (ModelState.IsValid)
{
Comment comment = new Comment(_userRepository.GetByUsername(User.Identity.Name));
Mapper.Map(newComment, comment);
_commentRepository.Add(comment);
_postsRepository.CommentAdded(comment.Article);
return RedirectToAction("Index", new { id = newComment.PostID });
}
// What do I do here?
}
I've tried several ways of returning views here but my issue is further complicated by some controller parameter validation that I have going on in the parent action:
//
// GET: /Posts/5/this-is-a-slug
public ActionResult Index(int id, string slug)
{
PostViewModel viewModel = new PostViewModel();
var model = _postsRepository.GetByID(id);
if (model != null)
{
if (slug == null || slug.CompareTo(model.Slug) != 0)
{
return RedirectToActionPermanent("Index", new { id, slug = model.Slug });
}
else
{
_postsRepository.PostVisited(model);
Mapper.Map(model, viewModel);
viewModel.AuthorName = _userRepository.GetById(model.AuthorID);
}
}
return View(viewModel);
}
This action basically mimics how SO's URLs work. If a post ID is supplied, the post is fetched from the database along with a slug which is created when the post is created. If the slug in the URL doesn't match the one in the database, it redirects to include the slug. This is working nicely but it does mean I'm having issues trying to populate my parent viewmodel, which is the following:
public class PostViewModel
{
public int PostID { get; set; }
public string Title { get; set; }
public string Body { get; set; }
public string Slug { get; set; }
public DateTime DatePublished { get; set; }
public int NumberOfComments { get; set; }
public int AuthorID { get; set; }
public string AuthorName { get; set; }
public List<CommentViewModel> Comments { get; set; }
public CommentViewModel NewComment { get; set; }
}
What I was hoping would work is to populate PostViewModel.NewComment, test to see if it has data and then using it to display any model errors. Unfortunately, I'm lost as to how to accomplish that. This question helped me shape my approach, but it didn't quite answer my problem.
Could someone give me a gentle push in the right direction? If my approach seems unreasonable, I'd love to find out why and what a potential fix would be.
Many thanks in advance.
Forgot to fill in my answer here. For anyone that happens to stumble on this, the answer was to use TempData to store the ModelState errors and then repopulating ModelState in the relevant controller action.
Firstly, I declared a key in the controller which would be used to reference the data inside TempData. I decided to base this on the CommentViewModel type as both actions depend on it.
public class PostsController : Controller
{
private static readonly string commentFormModelStateKey = typeof(CommentViewModel).FullName;
// Rest of class.
}
In this first action, the code checks to see if TempData contains data assigned to the key. If it does, it's copied into ModelState.
// GET: /posts/comment
[ChildActionOnly]
public PartialViewResult Comment(PostViewModel viewModel)
{
viewModel.NewComment = new CommentViewModel(viewModel.PostID, viewModel.Slug);
if (TempData.ContainsKey(commentFormModelStateKey))
{
ModelStateDictionary commentModelState = TempData[commentFormModelStateKey] as ModelStateDictionary;
foreach (KeyValuePair<string, ModelState> valuePair in commentModelState)
ModelState.Add(valuePair.Key, valuePair.Value);
}
return PartialView(viewModel.NewComment);
}
This action determines if the ModelState is valid before adding a comment to the database. If the ModelState is not valid, it is copied to TempData, which makes it available to the first action.
// POST: /posts/comment
[HttpPost, Authorize]
public ActionResult Comment(CommentViewModel newComment)
{
if (!ModelState.IsValid)
{
TempData.Add(commentFormModelStateKey, ModelState);
return Redirect(Url.ShowPost(newComment.PostID, newComment.Slug));
}
// Code to add a comment goes here.
}