Getting model data without a viewbag - c#

I'm an intern working on some quality-of-life improvements for a site. Nothing essential, but since this is actually going to be deployed I want to keep things tidy.
I need to pull a table from a database, and display it in in a shared header on a site. I already have the database set up, the model getting data from the database, and a test view just to see if it's displaying correctly. However, the model is being passed by the page controller through the viewbag, which I've been told not to do.
So, how can I have my page print out my model data without passing it through a viewbag?

You can do that using this format
public ActionResult Index(YourModelHere model)
{
return View(model);
}
In your view, add this
#model yournamespacehere.Models.YourModelHere
UPDATE
Create a new controller for partial view data
//partial view call
public ActionResult GetHeaderPartial()
{
var model = new DataModel();
model.data1 = "Menu 1";
model.data2 = "Menu 2";
return PartialView("_HeaderPartial", model);
}
Create the Partial View for the shared header. In this example, i've named it as "_HeaderPartial"
#model yournamespace.Models.DataModel
<li>#Model.data1</li>
<li>#Model.data2</li>
In your shared header layout, add #Html.Action("GetHeaderPartial","ControllerHere")
<header>
<div class="content-wrapper">
<div class="float-left">
<p class="site-title">#Html.ActionLink("your logo here", "Index", "Home")</p>
</div>
<div class="float-right">
<section id="login">
#Html.Partial("_LoginPartial")
</section>
<nav>
<ul id="menu">
<li>#Html.ActionLink("Home", "Index", "Home")</li>
<li>#Html.ActionLink("About", "About", "Home")</li>
<li>#Html.ActionLink("Contact", "Contact", "Home")</li>
#Html.Action("GetHeaderPartial","Sample")
</nav>
</div>
</div>
</header>

Here's a good place to start. As the article states, there are basically three ways to pass data to the view:
ViewBag
Dynamic objects
Strongly Typed objects
With the latter approach (generally a good idea), you just have to pass the model instance to the ActionResult you're returning (in your case, that would be a ViewResult instance).
So instead of this....
public ActionResult Index()
{
ViewBag.Foo= new Foo();
return View();
}
you do this...
public ActionResult Index()
{
return View(new Foo());
}
Make sure your model in the Index view has the following line:
#model Foo
Now you can use Razor or whatever syntax you're using to do what you need to do in your view.

Setup a view model that is created in the controller and passed to the view. This link will help you!

Related

Insert items to List in Razor with Model [duplicate]

I have added a button in my view. When this button is clicked partial view is added. In my form I can add as much partial view as I can. When Submitting this form data I am unable to send all the partial view data to controller.
I have made a different model having all the attributes and I have made a list of that model to my main model. Can anyone please give me some trick so that I can send all the partial view content to my controller?
In My View
<div id="CSQGroup">
</div>
<div>
<input type="button" value="Add Field" id="addField" onclick="addFieldss()" />
</div>
function addFieldss()
{
$.ajax({
url: '#Url.Content("~/AdminProduct/GetColorSizeQty")',
type: 'GET',
success:function(result) {
var newDiv = $(document.createElement("div")).attr("id", 'CSQ' + myCounter);
newDiv.html(result);
newDiv.appendTo("#CSQGroup");
myCounter++;
},
error: function(result) {
alert("Failure");
}
});
}
In My controller
public ActionResult GetColorSizeQty()
{
var data = new AdminProductDetailModel();
data.colorList = commonCore.getallTypeofList("color");
data.sizeList = commonCore.getallTypeofList("size");
return PartialView(data);
}
[HttpPost]
public ActionResult AddDetail(AdminProductDetailModel model)
{
....
}
In my Partial View
#model IKLE.Model.ProductModel.AdminProductDetailModel
<div class="editor-field">
#Html.LabelFor(model => model.fkConfigChoiceCategorySizeId)
#Html.DropDownListFor(model => model.fkConfigChoiceCategorySizeId, Model.sizeList, "--Select Size--")
#Html.ValidationMessageFor(model => model.fkConfigChoiceCategorySizeId)
</div>
<div class="editor-field">
#Html.LabelFor(model => model.fkConfigChoiceCategoryColorId)
#Html.DropDownListFor(model => model.fkConfigChoiceCategoryColorId, Model.colorList, "--Select Color--")
#Html.ValidationMessageFor(model => model.fkConfigChoiceCategoryColorId)
</div>
<div class="editor-field">
#Html.LabelFor(model => model.productTotalQuantity)
#Html.TextBoxFor(model => model.productTotalQuantity)
#Html.ValidationMessageFor(model => model.productTotalQuantity)
</div>
Your problem is that the partial renders html based on a single AdminProductDetailModel object, yet you are trying to post back a collection. When you dynamically add a new object you continue to add duplicate controls that look like <input name="productTotalQuantity" ..> (this is also creating invalid html because of the duplicate id attributes) where as they need to be <input name="[0].productTotalQuantity" ..>, <input name="[1].productTotalQuantity" ..> etc. in order to bind to a collection on post back.
The DefaultModelBinder required that the indexer for collection items start at zero and be consecutive, or that the form values include a Index=someValue where the indexer is someValue (for example <input name="[ABC].productTotalQuantity" ..><input name="Index" value="ABC">. This is explained in detail in Phil Haack's article Model Binding To A List. Using the Index approach is generally better because it also allows you to delete items from the list (otherwise it would be necessary to rename all existing controls so the indexer is consecutive).
Two possible approaches to your issue.
Option 1
Use the BeginItemCollection helper for your partial view. This helper will render a hidden input for the Index value based on a GUID. You need this in both the partial view and the loop where you render existing items. Your partial would look something like
#model IKLE.Model.ProductModel.AdminProductDetailModel
#using(Html.BeginCollectionItem())
{
<div class="editor-field">
#Html.LabelFor(model => model.fkConfigChoiceCategorySizeId)
#Html.DropDownListFor(model => model.fkConfigChoiceCategorySizeId, Model.sizeList, "--Select Size--")
#Html.ValidationMessageFor(model => model.fkConfigChoiceCategorySizeId)
</div>
....
}
Option 2
Manually create the html elements representing a new object with a 'fake' indexer, place them in a hidden container, then in the Add button event, clone the html, update the indexers and Index value and append the cloned elements to the DOM. To make sure the html is correct, create one default object in a for loop and inspect the html it generates. An example of this approach is shown in this answer
<div id="newItem" style="display:none">
<div class="editor-field">
<label for="_#__productTotalQuantity">Quantity</label>
<input type="text" id="_#__productTotalQuantity" name="[#].productTotalQuantity" value />
....
</div>
// more properties of your model
</div>
Note the use of a 'fake' indexer to prevent this one being bound on post back ('#' and '%' wont match up so they are ignored by the DefaultModelBinder)
$('#addField').click(function() {
var index = (new Date()).getTime();
var clone = $('#NewItem').clone();
// Update the indexer and Index value of the clone
clone.html($(clone).html().replace(/\[#\]/g, '[' + index + ']'));
clone.html($(clone).html().replace(/"%"/g, '"' + index + '"'));
$('#yourContainer').append(clone.html());
}
The advantage of option 1 is that you are strongly typing the view to your model, but it means making a call to the server each time you add a new item. The advantage of option 2 is that its all done client side, but if you make any changes to you model (e.g. add a validation attribute to a property) then you also need to manually update the html, making maintenance a bit harder.
Finally, if you are using client side validation (jquery-validate-unobtrusive.js), then you need re-parse the validator each time you add new elements to the DOM as explained in this answer.
$('form').data('validator', null);
$.validator.unobtrusive.parse($('form'));
And of course you need to change you POST method to accept a collection
[HttpPost]
public ActionResult AddDetail(IEnumerable<AdminProductDetailModel> model)
{
....
}

Checking model is null or not in mvc

I am trying to check the model is null OR not but I am not able to solve the issue.
While rendering the main view I have rendered the partial view as follows
Main View
<div class="modal fade" id="surveyPreviewModal" data-backdrop="static" data-keyboard="false" tabindex="-1" role="dialog" aria-labelledby="surveyPreviewLabel" aria-hidden="true">
<div class="modal-lg modal-dialog">
<div class="modal-content" id="surveyPreviewContent">
<div class="modal-header">
<button type="button" class="close" data-dismiss="modal" aria-hidden="true">
×
</button>
<h4 class="modal-title" id="surveyPreviewLabel">Survey Preview</h4>
</div>
<div class="modal-body" id="surveyPreviewBody">
#Html.Partial("_surveyPreview")
</div>
</div>
</div>
</div>
and in partial view I have function as below
#model LMS_TraineeSurveyPaginationViewModel
<script type="text/javascript">
function SurveyPreview(){
var surveyQuestionViewModel = #Html.Raw(Json.Serialize(Model.SurveyQuestionsViewModel.ToArray()));
var surveyQuestionOptionChoideViewModel= #Html.Raw(Json.Serialize(Model.SurveyQuestionOptionChoiceViewModel.ToArray()));
$.post('#Url.Action("SurveyPreview", "Survey")', { SurveyID : surveyID,` page : page },
function (data) {
$('#surveyPreviewBody').html('');
$('#surveyPreviewBody').html(data);
SetProgressBar(page,'#(Model==null?0: Model.Pager.TotalPages)');
}).fail(function () {
alert("error in GetTraineeSurvey");
}).success(function () {
});
}
</script>
So while rendering the partial view in this function(SurveyPreview) it is giving the error as model is null and straight away white screen shown. If I haven't called the function which is inside of partial view then why does it checking whether model is null OR not ? it should be whenever I execute function like on button click ?
I have a button on main view from where I am showing the bootstrap modal and on 'show' method of bootstrap modal I am returning the same partial view again to bind the data in ajax call.
Below code is written in partial view
$(document).ready(function () {
$('#surveyPreviewModal').on('show.bs.modal', function (e) {
surveyID = $(e.relatedTarget).attr('data-surveyID');
SurveyPreview(#SurveyPageTypePageNumber.StartPage,null);
});
})
and in controller
public ActionResult SurveyPreview(int SurveyID, int page)
{
------ some code ------
return PartialView("_SurveyPreview",viewModel);
}
Any help on this appreciated !
when you load Partial view using #Html.Partial("_surveyPreview") it required LMS_TraineeSurveyPaginationViewModel to be passed which are not supplied
so to call Partial view you need to write something like
#Html.Partial("_surveyPreview",new LMS_TraineeSurveyPaginationViewModel());
The partial view expects a model of type LMS_TraineeSurveyPaginationViewModel. But you are not passing any model object when rendering partialview from the main view.
In partialview function SurveyPreview() uses Model's properties. Since you are not passing any model object from the main view, Model is coming null in the partial view. That's why you are seeing NullReferenceException.
So you need to make sure that the partial view gets model.
You need to take different approach to render the partial view. You can use Html.Action to call the Action method which will return the partial view and render in the main view.
Replace following line in your main view
#Html.Partial("_surveyPreview")
with
#Html.Action("SurveyPreview", new { SurveyID = "<<somesoveryId>>", page = "<<somepage>>"})
This way I will call SurveyPreview action of the controller with the provided parameters and it will return the partial view with model and it will be rendered.
I am not sure of the what values to be passed in SurveyID and page parameters so I have placed placeholders there. You need to put appropriate values over there.

Redirecting parent page from Html.renderAction child without using Ajax, Java, Jquery or such

I have a problem where I have a form in a Html.RenderAction and after submitting the form I have to reload the parent but I keep getting "Child actions can not perform redirect actions". So how can I solve it without Ajax etc.
In my parent I have:
#{
var UserReviewExist = Model.Reviews.FirstOrDefault(x => x.AspNetUser.UserName == Name.AspNetUser.UserName);
}
#{if (UserReviewExist == null)
{
Html.RenderAction("ReviewCreate", "Reviews", new { BookID = Model.Id });
}
}
My RenderAction View contains this:
#model Trigger_Happy_Bunnies.Models.Review
#{
Layout = null;
}
#{
if (true)
{
Trigger_Happy_Bunnies.Models.Review newReview = new Trigger_Happy_Bunnies.Models.Review();
<div style="border:1px black">
#using (Html.BeginForm())
{
#Html.AntiForgeryToken()
<div class="form-horizontal">
#Html.ValidationSummary(true, "", new { #class = "text-danger" })
and ends with
<div class="form-group">
<div class="col-md-offset-2 col-md-10">
<input type="submit" value="Create" class="btn btn-default" />
</div>
</div>
</div>
}
</div>
}
}
#section Scripts {
#Scripts.Render("~/bundles/jqueryval")
}
And lastly I have this in my controller:
[HttpPost]
[ValidateAntiForgeryToken]
public ActionResult ReviewCreate([Bind(Include = "Id,BookId,UserId,Text,Title,Rating,IsActive,IsReported,ReportedBy,ReportReason,ModifiedDate,ModifiedBy,CreatedDate")] Review review)
{
if (ModelState.IsValid)
{
db.Reviews.Add(review);
db.SaveChanges();
return View("~/Views/Reviews/ReviewCreate.cshtml");
}
ViewBag.UserId = new SelectList(db.AspNetUsers, "Id", "Email", review.UserId);
ViewBag.BookId = new SelectList(db.Books, "Id", "UserId", review.BookId);
return PartialView();
}
So how can I update the parent view when submitting the form?
I'm not sure what your issue is here. A child action merely dumps its response into the view. So at the end of the day, whether you used a child action, a partial or just plopped the code right in the view, you just have a one HTML document that includes a form.
Calling Html.BeginForm with no parameters says basically that it should use the current action, but even in the context of child action, that's still going to be the main action being rendered. So, your form will post to that main action, not your child action.
That's how it should be. You cannot post to a child action, because that makes no sense in the context of a web page. Technically, you can as long as it's not marked as [ChildActionOnly], but the entire page will change to the partial view that's returned as the response, sans layout. If you want to replace just the area that was rendered via the child action, you must submit an AJAX request that returns the partial response and manually replace the appropriate node in the DOM with that.
In other words, that's why a child action can't redirect. It's not a true action and it hasn't been routed to. It's not rendered until the response preparation phase, and by that point, there's already data in the response, preventing any changes, like a redirect. If you need to redirect after the post of the form, you should have that already in place, just make sure your main action has a version that handles post, and redirect from there.

MVC 4 - LogOff controller action giving 404 not found

I'm just wrapping up a college project, I'm not sure if I've been staring at my computer for too long and am missing something obvious, but when I try to log a user out, I'm getting a 404 not found for the URL /Account/LogOff.
I have a navbar that shows Log in/Log out depending on whether a user is, logged in, or, logged out:
<div class="nav-collapse collapse">
<ul class="nav pull-right">
<li class="dropdown" id="dropdown-login-div">
#if (!Request.IsAuthenticated)
{
<a class="dropdown-toggle" href="#" data-toggle="dropdown">Sign In <strong class="caret"></strong></a>
}
else
{
#Html.ActionLink("Log Off", "LogOff", "Account")
}
<div class="dropdown-menu" id="dropdown-login">
#Html.Partial("~/Views/Account/_LoginPartial.cshtml", new ViewDataDictionary<LetLord.Models.LoginModel>())
</div>
</li>
</ul>
</div>
In my Account controller the default LogOff action that comes with the Internet template:
[HttpPost]
[ValidateAntiForgeryToken]
public ActionResult LogOff()
{
WebSecurity.Logout();
return View("Index");
}
Could someone tell me why this happening - before I throw my laptop against the wall. Cheers.
You use a link (<a/> tag) to log off which results in HTTP GET request when user clicks on it, but your action is constrained to serve POST request only (because it is decorated with [HttpPost] attribute).
You either need to put your link into a form and generate POST request, or remove [HttpPost] and [ValidateAntiForgeryToken] (credits to GalacticCowboy) from your action.
Since logout modifies server state, I wouldnt remove [HttpPost] and [ValidateAntiForgeryToken]
Instead I will replace the link (anchor tag) with the following
#using (Html.BeginForm("Log Out", "Account", FormMethod.Post,
new { id = "logoutForm" }))
{
#Html.AntiForgeryToken()
Log Out
}
I ran into this issue on a legacy app. The way that I fixed it was to detect when the supplied return Url was '/Account/LogOff' and act accordingly.
From the 'AccountController.cs' file, 'Login' method:
if (returnUrl == "/Account/LogOff")
{
return this.RedirectToLocal(null);
}
else
{
return this.RedirectToLocal(returnUrl);
}

MVC project, Error because model not passed to view?

The Scenario is this...I goto the login screen on my web app. I login with wrong username/password. I get an error on the line in my HTML for tab-6 #HTML.Partial("../../Account/Register") because the RegisterModel is not passed in(Exact error below). Well I'm not doing anything with the Register screen, just logging in.
However, I think the problem is because the Register section is always loaded. So I'm thinking, I don't need to pass in both LogOnModel and RegisterModel, to fix the problem, but maybe should not load the Register page until it is specifically called, I think? Is that the correct thing to do and how would I load the Register page only when it is called/clicked. I'm using JQuery UI tabs and asp.net mvc.
ERROR:
The model item passed into the dictionary is of type 'Portal.Web.Models.LogOnModel', but this dictionary requires a model item of type 'Portal.Web.Models.RegisterModel'.
Thanks a lot!
<div id="tabs" class="ui-tabs ui-widget ui-widget-content ui-corner-all" style=" position:relative; border:0px;" >
<ul class="ui-tabs-nav">
<li><a href="#tabs-1" >Home</a></li>
<li><a href="#tabs-2" >Statistics</a></li>
<li><a href="#tabs-3" >Topo Maps</a></li>
<li><a href="#tabs-4" >FAQs</a></li>
<li style="display:none;">Login</li>
<li style="display:none;">SignUp</li>
</ul>
<div id="tabs-1" class="ui-tabs-hide ui-tabs-panel">#Html.Partial("../Home/Home") </div>
<div id="tabs-2" class="ui-tabs-hide ui-tabs-panel">#Html.Partial("../Statistics/Statistics")</div>
<div id="tabs-3" class="ui-tabs-hide ui-tabs-panel">#Html.Partial("../Maps/Maps")</div>
<div id="tabs-4" class="ui-tabs-hide ui-tabs-panel">#Html.Partial("../Home/FAQs")</div>
<div id="tabs-5" class="ui-tabs-hide ui-tabs-panel">#Html.Partial("../Account/LogOn")</div>
<div id="tabs-6" class="ui-tabs-hide ui-tabs-panel">#Html.Partial("../Account/Register")</div>
</div>
Here is my AccountController with action method
[HttpPost]
public ActionResult LogOn(LogOnModel model, string returnUrl)
{
if (ModelState.IsValid)
{
if (MembershipService.ValidateUser(model.UserName, model.Password))
{
FormsService.SignIn(model.UserName, model.RememberMe);
if (Url.IsLocalUrl(returnUrl))
{
return Redirect(returnUrl);
}
else
{
return RedirectToAction("Index", "Home");
}
}
else
{
ModelState.AddModelError("", "The user name or password provided is incorrect.");
}
}
return View(model);
}
As Requested Here is the additional code:
public ActionResult LogOn()
{
return View();
}
Double-check that you've set your partial view to inherit from the appropriate model. At the top of your partial view code, you should have a line like this.
<%# Control Language="C#" Inherits="System.Web.Mvc.ViewUserControl<Portal.Web.Models.LogOnModel>" %>

Categories