Pass model to PartialAsync view (Razor, MVC) - asp.net-mvc

I have AccountController.cs with below action:
[HttpGet]
[AllowAnonymous]
public IActionResult Register()
{
ViewBag.Registration = GetRegistration();
return View();
}
ViewBag.Registration contains 2 elements and it's ok.
Then I got Registration.cshtml view:
#model Registration <!-- this model I'm using for other form -->
#{
Layout = "_Layout";
}
<!-- some code -->
#await Html.PartialAsync("AllRegistered")
and AllRegistered.cshtml where data from ViewBag.Registration should be displayed:
#model IEnumerable<Registration>
<table>
<tr>
<th>#Html.DisplayNameFor(m => m.Email)</th>
<th>#Html.DisplayNameFor(m => m.City)</th>
</tr>
#if (Model != null && Model.Count() != 0)
{
#foreach (Registration registration in Model)
{
<tr>
<th>#Html.DisplayFor(m => registration.Email)</th>
<th>#Html.DisplayFor(m => registration.City)</th>
</tr>
}
}
</table>
But nothing is generated into view, Model I think is empty.

The PartialAsync method contains an overload which includes the model:
Html.PartialAsync(string partialViewName, TModel model)
You should include the IEnumerable<Registration> (the partial view's model) in that helper.
If GetRegistrations() is returning that IEnumerable, you would define the partial view like this:
#await Html.PartialAsync("AllRegistered", (List<Registration>)ViewBag.Registration)

While Nathan's answer is perfectly correct, it would be more appropriate for this to be a view component. The fact that you want to display all registrations is a view detail that has nothing to do with the purpose of this action. As such, having the action be responsible for retrieving data requires it to have knowledge it doesn't need and shouldn't have.
Instead, add a class like:
public class AllRegistrationsViewComponent : ViewComponent
{
private readonly RegistrationsService _service;
public AllRegistrationsViewComponent(RegistrationService service)
{
_service = service;
}
public async Task<IViewComponentResult> InvokeAsync()
{
// logic behind `GetRegistrations()` here
return View(registrations);
}
}
The reference here to RegistrationsService is just whatever means you're using to retrieve your registrations, to show how to inject that into the component. This might be your context or something else entirely.
Then, create the view Views/Components/AllRegistrations/Default.cshtml with:
#model IEnumerable<Registration>
<table>
<tr>
<th>#Html.DisplayNameFor(m => m.Email)</th>
<th>#Html.DisplayNameFor(m => m.City)</th>
</tr>
#if (Model != null && Model.Count() != 0)
{
#foreach (Registration registration in Model)
{
<tr>
<th>#Html.DisplayFor(m => registration.Email)</th>
<th>#Html.DisplayFor(m => registration.City)</th>
</tr>
}
}
</table>
The AllRegistrations part of the path is based on the name of the view component, without the ViewComponent part, so if you name it differently, adjust here as well.
Finally, in your view:
#await Component.InvokeAsync("AllRegistrations")
Then, your action can just focus on its actual purpose:
[HttpGet]
[AllowAnonymous]
public IActionResult Register()
{
return View();
}

Related

How can I check if a check box is checked in view page with razor in asp.net mvc?

I'm new in asp.net mvc and write this razor code for show me check box component:
#Html.CheckBox("FlatFile",false)
I want write razor c# script to check if that check box is checked then work some thing, I don't want to write javascript or anything, I just want write c# razor script code for that purpose.
How can I write that?
You can check this using FormCollection in your controller action.
try using below code:
[HttpPost]
public ActionResult Index(FormCollection frm)
{
bool MyBoolValue = Convert.ToBoolean(frm["FlatFile"].Split(',')[0]);
return View();
}
Your checkbox code:
#Html.CheckBox("FlatFile",false)
Now you can use the CheckBox name (i.e., FlatFile) to do stuff in your controller like :
public ActionResult Something(IEnumerable<bool> FlatFile)
{
if(FlatFile!= null) --you can give your condition here
{
--do something
}
else
{
--do something else
}
}
A sample example:-
This is my controller code:-
public ActionResult test()
{
return View();
}
[HttpPost]
public ActionResult test(bool FlatFile)
{
if(FlatFile==true)
{
ViewBag.Message = "Selected";
return View();
}
else (FlatFile == false)
{
ViewBag.Message="Not selected";
return View();
}
}
This is my view code(i.e., test.cshtml):-
#using (Html.BeginForm("test", "User", FormMethod.Post,
new { enctype = "multipart/form-data" }))
{
#Html.AntiForgeryToken()
<fieldset>
<legend>Test CheckBox</legend>
<table>
<tr>
<td>#Html.Label("Select"):</td>
<td>#Html.CheckBox("FlatFile", false)</td>
</tr>
<tr>
<td colspan="2"><input type="submit" value="Check" /></td>
</tr>
<tr>
<td>#ViewBag.Message</td>
</tr>
</table>
</fieldset>
}

ViewModel not posting back

I have this in my controller:
public ActionResult Index()
{
var viewModels = _dataSyncService.Get().Select(provider => new IndexViewModel
{
Selected = false, Provider = provider
}).ToList();
return View(viewModels);
}
[HttpPost]
[ValidateAntiForgeryToken]
public ActionResult Index(IEnumerable<IndexViewModel> viewModels)
{
//Breakpoint on this to check viewModels
}
ViewModel:
public class IndexViewModel
{
public bool Selected { get; set; }
public IDataSyncProvider Provider { get; set; }
}
And my Index.cshtml:
#using (Html.BeginForm())
{
#Html.AntiForgeryToken()
#Html.ValidationSummary(true)
<button type="submit" class="btn blue">Trigger Selected</button>
#foreach (var sync in Model)
{
<tr>
<td>
#Html.CheckBoxFor(s => sync.Selected)
</td>
<td>#sync.Provider.FriendlyName</td>
</tr>
}
}
But my models are posted back, viewModels always comes back as null. I read alot about having to assign Id's to the check box etc but I thought that's what Html.CheckBoxFor is for.
I'm sure I'll be kicking myself about this at some point but I could do with some guidance please.
If you want to use the built in Html helpers like Html.CheckBoxFor then you need to use a for loop instead of the foreach in order to the helpers render the correct input names:
#for(int sync = 0; sync < Model.Count; sync++)
{
<tr>
<td>
#Html.CheckBoxFor(m => Model[sync].Selected)
</td>
<td>#Model[sync].Provider.FriendlyName</td>
</tr>
}
You can read more about binding to lists here: Model Binding To A List
This is because, you are just redirecting it using return RedirectToAction("Index");
In your POST action
try :
[HttpPost]
[ValidateAntiForgeryToken]
public ActionResult Index(IEnumerable<IndexViewModel> viewModels)
{
return View(viewModels);
}
Hope will help.

Multiple Actions per View consisting of partial views bound to models

Extension to: How do you handle multiple submit buttons in ASP.NET MVC Framework?
Let us say a view is composed of partial views bound with related models, let's say a student is required to provide multiple contact persons(partial view bound to Person model) and multiple contact numbers(partial view bound to a model) to get registered, sorry for the bad example. Once a contact person or number is added, an action (child postback or whatever) is called which validates the related model (not student model), adds it in a list and returns to the same page for further processing. Once all are added, the parent/master action validates whole student model and processes it.
How to validate the specific model for which an action is being called, add it to the page and return the same student view with added values in response?
This solution uses #2 (Session) since its simpler to code however this demonstrates the principles.
Views
Index View:
#using StackOverflow.Models
<div>
#{ Html.RenderPartial("PersonGrid", Model.Persons, new ViewDataDictionary()); }
#Html.Partial("NewPerson", new Person())
#{ Html.RenderPartial("ContactGrid", Model.Contacts, new ViewDataDictionary()); }
#Html.Partial("NewContact", new Contact())
#using(Html.BeginForm("Validate", "Home", FormMethod.Post))
{
<input type="submit" value="Validate" />
}
</div>
Person Grid
#model IList
<table>
<thead>
<tr>
<td>First Name</td>
<td>Last Name</td>
</tr>
</thead>
<tbody>
#if (Model != null && Model.Any())
{
foreach (var person in Model)
{
<tr>
<td>#person.FirstName</td>
<td>#person.LastName</td>
</tr>
}
}
else
{
<tr>
<td colspan="2" style="text-align: center">No persons available</td>
</tr>
}
</tbody>
</table>
Contact Grid
#model IList
<table>
<thead>
<tr>
<td>Phone</td>
</tr>
</thead>
<tbody>
#if (Model != null && Model.Any())
{
foreach (var contact in Model)
{
<tr>
<td>#contact.Phone</td>
</tr>
}
}
else
{
<tr>
<td>No contacts available</td>
</tr>
}
</tbody>
</table>
New Person
#model StackOverflow.Models.Person
#using (Html.BeginForm("NewPerson", "Home", FormMethod.Post))
{
<div>
#Html.Hidden("PersonViewState", TempData["PersonViewState"])
#Html.LabelFor(m => m.FirstName)<br />
#Html.TextBoxFor(m => m.FirstName)<br />
<br />
#Html.LabelFor(m => m.LastName)<br />
#Html.TextBoxFor(m => m.LastName)<br />
<br />
<input type="submit" value="Submit" />
</div>
}
New Contact
#model StackOverflow.Models.Contact
#using (Html.BeginForm("NewContact", "Home", FormMethod.Post))
{
<div>
#Html.LabelFor(m => m.Phone)<br />
#Html.TextBoxFor(m => m.Phone)<br />
<br />
<input type="submit" value="Submit" />
</div>
}
Models
public class Person
{
[Display(Name = "First Name")]
public string FirstName { get; set; }
[Display(Name = "Last Name")]
public string LastName { get; set; }
}
public class Contact
{
[Display(Name = "Phone")]
public string Phone { get; set; }
}
public class HomeModel
{
public IList<Person> Persons { get; set; }
public IList<Contact> Contacts { get; set; }
}
Helpers
public static class PersistenceMechanism
{
public static IList GetPersons()
{
return (IList<Person>) HttpContext.Current.Session["__Persons"];
}
public static IList GetContacts()
{
return (IList<Contact>) HttpContext.Current.Session["__Contacts"];
}
public static void Update(IList<Person> persons)
{
HttpContext.Current.Session["__Persons"] = persons;
}
public static void Update(IList<Contact> contacts)
{
HttpContext.Current.Session["__Contacts"] = contacts;
}
}
Controller
public class HomeController : Controller
{
public ActionResult Index()
{
var model = new HomeModel
{
Persons = PersistenceMechanism.GetPersons(),
Contacts = PersistenceMechanism.GetContacts()
};
return View(model);
}
[HttpGet]
public ActionResult PersonGrid()
{
var persons = PersistenceMechanism.GetPersons();
return PartialView(persons);
}
[HttpGet]
public ActionResult ContactGrid()
{
var contacts = PersistenceMechanism.GetContacts();
return PartialView(contacts);
}
[HttpPost]
public ActionResult NewPerson(Person model)
{
var persons = PersistenceMechanism.GetPersons() ?? new List<Person>();
persons.Add(model);
PersistenceMechanism.Update(persons);
return RedirectToAction("Index");
}
[HttpPost]
public ActionResult NewContact(Contact model)
{
var contacts = PersistenceMechanism.GetContacts() ?? new List<Contact>();
contacts.Add(model);
PersistenceMechanism.Update(contacts);
return RedirectToAction("Index");
}
[HttpPost]
public ActionResult Validate()
{
var persons = PersistenceMechanism.GetPersons();
var contacts = PersistenceMechanism.GetContacts();
// validate
// ...
return RedirectToAction("Index");
}
}
To repeat the question to ensure that I have the idea of what you are asking.
Your page page is build from two partial views with different models. Both of these partial views contain a form which will on submit build a grid of records on the UI. The main page will have a further validate button which will then validate the entire contents of both grids on postback?
In this situation I will like to have two forms where the submit event is powered by Ajax. Either jQuery / Microsoft Ajax. Both forms will submit to two separate actions which will accept their respective models, Person and Contact. Each submit button will return its respective partial view which will be a grid showing the collection of items that have been submitted so far. The partial view returned will update a specified target (e.g. div) since we are using AJAX.
Of course it will be necessary to remember the previous items that were submitted so far in order reconstruct the grid with the new item added. This will mean some soft of persistent storage is required. Some options available are:
Database
Session
Hidden form field(s) (preferred). It is possible to use the array model binding mechanism to support this or a simple serialized object (or list of objects) in a single hidden field.
Validation now becomes simple as the model are available server side through your persistent storage mechanism on postback.

Model passed to a partial view is null upon form submit

I have the following model:
#model SmartSEOModel
public class SmartSEOModel
{
public SmartSEOSettingsModel SmartSEOSettingsModel { get; set; }
public SEOTemplateModel SEOTemplateModel { get; set; }
}
In my view I have a partial view which I call like this:
#using (Html.BeginForm())
{
some razor code here
<div id="pnlSmartSEO">
#Html.Partial(ViewNames.SmartSEOController_SEOTemplate, Model.SEOTemplateModel)
</div>
}
In the partial view there are some form fields bound to the SEOTemplateModel.
The problem is that when I receive the SmartSEOModel in my HttpPost action, the SEOTemplateModel is null. As if the SEOTemplateModel has been passed by copying it to the partial view.
Please advise why this is and how to workaround it.
Many thanks
My partial view looks like this:
#Html.Telerik().TabStrip().Name("SmartSEO").Items(x =>
{
x.Add().Text(T("Admin.SmartSEO").Text).Content(GetSmartSEOUI().ToHtmlString()).Selected(true);
})
#helper GetSmartSEOUI()
{
#(Html.LocalizedEditor<SEOTemplateModel, SEOTemplateLocalizedModel>("SmartSEO-Localized",
#<table class="adminContent">
<tr>
<td class="adminTitle">
#Html.NopLabelFor(model => model.Locales[item].CategoryTitleSEOTemplate):
</td>
<td class="adminData">
#Html.EditorFor(model => model.Locales[item].CategoryTitleSEOTemplate)
</td>
</tr>
</table>,
#<table class="adminContent">
<tr>
<td class="adminTitle">
#Html.NopLabelFor(model => model.CategoryTitleSEOTemplate):
</td>
<td class="adminData">
#Html.EditorFor(model => model.CategoryTitleSEOTemplate)
</td>
</tr>
</table>
))
}
My HttpPost action looks like this:
[HttpPost]
public ActionResult Configure(SmartSEOModel smartSEOModel)
{
var seoTemplate = SEOTemplateService.GetSEOTemplateById(smartSEOModel.SEOTemplateModel.Id);
if(seoTemplate == null)
{
throw new ArgumentException(String.Format("No SEOTemplate found with Id {0}", smartSEOModel.SEOTemplateModel.Id));
}
if (!ModelState.IsValid)
{
RedirectToAction("Configure");
}
SettingService.SaveSetting(smartSEOModel.SmartSEOSettingsModel.ToEntity());
seoTemplate = smartSEOModel.SEOTemplateModel.ToEntity(seoTemplate);
SEOTemplateService.UpdateSEOTemplate(seoTemplate);
UpdateLocales(seoTemplate, smartSEOModel.SEOTemplateModel);
//activity log
CustomerActivityService.InsertActivity("EditSEOTemplate", LocalizationService.GetResource("ActivityLog.EditSEOTemplate"));
SuccessNotification(LocalizationService.GetResource("SevenSpikes.NopSmartSEO.Admin.SEOTemplate.Notifications.SEOTemplateEdited"));
return View("SevenSpikes.Nop.Plugins.SmartSEO.Views.Configure", smartSEOModel);
}
Becuase you don't have a form within your partial view, it will not persist the data. Try using #Html.EditorFor instead of #Html.Partial.
So your main view would look like
#using (Html.BeginForm())
{
some razor code here
<div id="pnlSmartSEO">
#Html.EditorFor(model => model.SEOTemplateModel)
</div>
}
You would then need to move your partial view into a template. Rename your partial view to EditorTemplates\SEOTemplateModel.cshtml and place it in the same location where your main view is.
You will also need to make your template strongly typed: #model [namespace].SEOTemplateModel

MVC Partial Views in Site.Master

I am trying to render a partial view in my Site.Master file but I keep on getting an error (Object reference not set to an instance of an object). If I access the view directly, I get the info that I need, but if I access the Index View from the HomeController, I get an error. The partial view will display the logged in user, department, and role. This is my code.
Controllers Folder
-HomeController.cs
-UsersController.cs
Models
-Repository Folder
- UersRepository.cs
-Repository Interface Folder
- IUsers.cs
-Service Folder
- UsersService.cs
- IUserService.cs
-Validation Folder
- IValidationDictionary
- ModelStateWrapper
The view that I'm trying to partially render is called LoginInfo.ascx and it's located in the Shared Folder.
LoginInfo.cs Code:
<%# Control Language="C#" Inherits="System.Web.Mvc.ViewUserControl<IEnumerable<Users>>" %>
<%# Import Namespace="TimeAttendanceMVC.Models"%>
<table>
<tr>
<th>
UserName
</th>
<th>
Department
</th>
<th>
UserType
</th>
</tr>
<% foreach (var item in Model) { %>
<tr>
<td>
<%: Html.DisplayFor(modelItem => item.UserName) %>
</td>
<td>
<%: Html.DisplayFor(modelItem => item.Department) %>
</td>
<td>
<%: Html.DisplayFor(modelItem => item.UserType) %>
</td>
</tr>
<% } %>
</table>
Site.Master Code:
<% Html.RenderPartial("LoginInfo"); %>
UserController.cs
public class UsersController : Controller
{
//
private IUsersService _service;
//==============================================================================
//==============================================================================
public UsersController()
{
_service = new UsersService(new ModelStateWrapper(this.ModelState));
}
public UsersController(IUsersService service)
{
_service = service;
}
//==============================================================================
//==============================================================================
// GET: /Employee/
//==============================================================================
//==============================================================================
// GET: /Employee/
public ActionResult Index()
{
var model = _service.Return_UserName_Dept();
return View("LoginInfo", model);
}
}
HomeController.cs
public ActionResult Index()
{
//var model = _service.Return_UserName_Dept();
//return View(model);
return View();
}
With the code above, I get an error. If I un-comment the 2 lines in my HomeController and I pass the model to the View, then it works fine. But my HomeController will need a different model, so how will I pass 2 models to the View?
Any ideas what I'm doing wrong? I'm still learning MVC right now so I'm not that good at this. Any help would be appreciated. Thanks.
Calling RenderPartial renders the view directly, inheriting the parent's model.
You should call RenderAction instead to render a child action so that the action can pass the correct model to the partial view.
Your Controller is able to pass a single Model to the View for rendering. Rather than attempting to have two models, or to call two Action Methods in a request with RenderAction, have you considered creating a parent ViewModel type?
// This model is passed to your view for rendering
public class MyViewModel
{
public string Title { get; set; }
public DateTime Date { get; set; }
// This property is passed as the model to your partial view
public UserInfo UserInfo { get; set; }
}
public ActionResult Index()
{
var model = new MyViewModel(); // _service.GetIndexViewModel();
model.UserInfo = _service.Return_UserName_Dept();;
return View(model);
}
Then in your View, you would do something like:
<% Html.RenderPartial("LoginInfo", Model.UserInfo); %>

Resources