MVC: Access data attributes in controller - asp.net-mvc-4

Might be a asked and answered...sorry but, I've been searching for a while.
RAZOR VIEW
#foreach (var item in Model.LanguageList)
{
<li>
<a href="#Url.Action("ChangeLanguage", "UserHeader")" data_languageID="#item.LanguageID" data_someval="hello" data_somevalb="world">
<div class="flag #item.LanguageFlag">
</div>
<div class="flag-title"> #item.LanguageName</div>
</a>
</li>
}
How do I access the data attributes in my controller?
CONTROLLER
public ActionResult ChangeLanguage()
{
var x = ControllerContext; //// ??? get the collection of [data-xx] from where?
return RedirectToAction("Buttons", "Designer");
}

data-* attributes are client side values and are not sent in the request.
In order to send those values, add them as route values
#foreach (var item in Model.LanguageList)
{
<li>
<a href="#Url.Action("ChangeLanguage", "UserHeader", new { languageID=item.LanguageID, someval="hello", somevalb="world" })">
<div class="flag #item.LanguageFlag"></div>
<div class="flag-title"> #item.LanguageName</div>
</a>
</li>
}
and include parameters in your GET method for the values
public ActionResult ChangeLanguage(int LanguageID, string someval, string somevalb)
Alternatively you could handle this using javascript/jquery (the ChangeLanguage() method also needs to be modified as shown above)
$('a').click(function() {
// get the url
var url = $(this).attr('href');
// get the data attributes
var languageID = $(this).data('languageID');
var someVal= $(this).data('someval');
var someValB= $(this).data('somevalb');
location.href = url + '?languageID=' + languageID + '&someVal =' + someVal + '&someValB=' + someValB;
return false; // cancel the default redirect
});

Related

Blazor. NavLink is not working in a foreach block when clicking multiply times

I created list of NavLink, this is only one difference between them, dynamic parameter Id
<div>
#foreach (var service in pageGlobal.Person.Services)
{
var link = $"service_description/{service.Identifier}";
<ul class="nav flex-column">
<li class="nav-item px-3">
<NavLink class="nav-link" href="#link">
#service.Name
</NavLink>
</li>
</ul>
}
</div>
And it works only if I click first time(no matter what NavLink item it works properly).
Click to the next link - url is changed but nothing happens.
Code of the ServiceDescription.razor
#page "/service_description/{Id:int}"
#using Site.Data
#using Site.ViewModels
#inject StartUpService page
#if (service != null)
{
<div> <h3>Service Name - #service.Name</h3> - <h4>#service.Description</h4> </div>
}
#code
{
[Parameter]
public int Id { get; set; }
PageGlobal pageGlobal;
Service service;
protected override async Task OnInitializedAsync()
{
pageGlobal = await page.GetPageGlobalAsync();
service = pageGlobal.Person.Services.Where(e => e.Identifier == Id).FirstOrDefault();
}
}
How can I force client reload ServiceDescription.razor with a new parameter using NavLink functionality?
This issue is caused by OnInitializedAsync. For OnInitializedAsync, it will be called only when the component is invoked when the component is ready to start.
If you want to change service based on parameter, you should use OnParametersSetAsync like below:
protected override async Task OnParametersSetAsync()
{
pageGlobal = new PageGlobal
{
Person = new Person()
{
Services = new List<Service>(){
new Service{ Identifier = 1, Name = "Test1", Description = "D1" },
new Service{ Identifier = 2, Name = "Test2" , Description = "D2" },
new Service{ Identifier = 3, Name = "Test3", Description = "D3" }
}
}
};
service = pageGlobal.Person.Services.Where(e => e.Identifier == Id).FirstOrDefault();
}
You could check ComponentBase

How do I turn Pagination Off?

I am using the MVC Widget Template to read a list of Dynamic Content, but need to be able to query the list. Because it has pagination, it only goes through the first 20 or so. How do I turn this off?
I Tried using this but didnt work:
<telerik:RadListView ID="KBList" ItemPlaceholderID="ItemsContainer" runat="server"
EnableEmbeddedSkins="False" EnableEmbeddedBaseStylesheet="False"
AllowPaging="False">
My Code Here:
#model Telerik.Sitefinity.Frontend.DynamicContent.Mvc.Models.DynamicContentListViewModel
#using Telerik.Sitefinity.Frontend.DynamicContent.WidgetTemplates.Fields.Helpers;
#using Telerik.Sitefinity;
#using Telerik.Sitefinity.Data.ContentLinks;
#using Telerik.Sitefinity.Frontend.Mvc.Helpers;
#using Telerik.Sitefinity.Frontend.Mvc.Models;
#using Telerik.Sitefinity.Web.DataResolving;
#using Telerik.Sitefinity.Model.ContentLinks;
#using Telerik.Sitefinity.Modules.Pages;
#Html.Script(ScriptRef.JQuery, "top", false)
<div class="#Model.CssClass">
<ul>
#foreach (var item in Model.Items)
{
var navigateUrl = HyperLinkHelpers.GetDetailPageUrl(item, ViewBag.DetailsPageId, ViewBag.OpenInSamePage, Model.UrlKeyPrefix);
<li #Html.InlineEditingAttributes(Model.ProviderName, Model.ContentType.FullName, (Guid)item.Fields.Id)>
<h3>
<a #Html.InlineEditingFieldAttributes("Title", "ShortText") href="#navigateUrl">
#item.Fields.Title
</a>
</h3>
</li>
}
</ul>
#if (Model.ShowPager)
{
#Html.Action("Index", "ContentPager", new { currentPage = Model.CurrentPage,
totalPagesCount = Model.TotalPagesCount.Value,
redirectUrlTemplate = ViewBag.RedirectPageUrlTemplate })
}
</div>
You need to edit the widget in page edit mode and there find the List Settings tab and once there you select the No Limit and Paging option.
This way, all items will be sent to the view.
Try writing your own MVC widget. In the Index method of your controller try fetching all content using resolveType of the dynamic module and (if needed convertinto required model) and pass it to your view file.
public ActionResult Index()
{
DynamicModuleManager dynamicModuleManager = DynamicModuleManager.GetManager(String.Empty,"SomeName");
Type dynArticleType = TypeResolutionService.ResolveType(resolveType);
var FilteredCollection = dynamicModuleManager.GetDataItems(dynArticleType)
.Where(x => x.Status == ContentLifecycleStatus.Live && x.Status != ContentLifecycleStatus.Deleted
&& x.Status != ContentLifecycleStatus.Temp && x.Status != ContentLifecycleStatus.Master)
.OrderByDescending(x => x.GetValue<System.DateTime>("PublicationDate"));
var fullList = FilteredCollection.ToList();
//Convert into customModel.
return View("Default", yourCustomModel);
}

How to save Dictionary object to database in MVC 4?

So, I am sending a dictionary object to my view from the controller.
// GET: QuestionResponses/Create
public ActionResult Create(int questionnaireUID)
{
var questions = from q in db.QUESTIONS
where q.QuestionnaireUID == questionnaireUID
select q;
ViewBag.NextQuestion = from q in db.QUESTIONS
where q.QuestionnaireUID == questionnaireUID
select new SelectListItem
{
Selected = (q.QuestionnaireUID == questionnaireUID),
Text = q.Question1,
Value = q.QuestionUID.ToString()
};
Dictionary<QUESTION, QUESTION_RESPONSES> dict = new Dictionary<QUESTION, QUESTION_RESPONSES>();
foreach (var question in questions)
{
dict.Add(question, new QUESTION_RESPONSES { QuestionUID = question.QuestionUID, Response = "", NextQuestion = "" });
}
return View(dict);
}
The reasoning behind this is that I need to view data from one model and need to add/edit data from another model. I tried using Tuples and was not able to get it to work (if you could tell me how to do this with Tuples, that would be helpful too).
This is what the view does with this Dictionary object.
<div class="form-group">
<h2> Reponses </h2>
<p> For each question, enter in the appropriate response(s). All questions must have at least one response. <p>
<div id="editorRows">
<div class="rows_no_scroll">
#foreach (var item in Model.ToArray())
{
<!-- The question that responses are being added to. -->
Html.RenderPartial("QuestionRow", item.Key);
<!-- All questions pertaining to this questionnaire. -->
// <p>Please select the question which should be asked as a response to this question.</p>
#Html.DropDownList("NextQuestion", null, htmlAttributes: new { #class = "form-control", id = "ddl_questions_" + count})
#Html.ValidationMessageFor(model => item.Value.NextQuestion, "", new { #class = "text-danger" })
<!-- The next question link and responses being inputted by user. -->
Html.RenderPartial("ResponseEditorRow", item.Value);
// <p> Question #count </p>
count += 1;
}
</div> <!--/rows_no_scroll-->
</div> <!-- /editorRows -->
</div> <!-- /form-group -->
For completeness, here are what the partial views are doing.
QuestionRow:
<div class="questionRow">
<!-- Hide attribute(s) not being viewed/edited. -->
#Html.HiddenFor(model => model.QuestionUID)
#Html.HiddenFor(model => model.QuestionnaireUID)
<!-- Show attribute(s) being viewed. -->
#Html.DisplayFor(model => model.Question1)
<div class="addQuestion">Add Response</div>
</div>
ResponseEditorRow:
<div class="editorRow">
#using (Html.BeginCollectionItem("questions"))
{
<!-- Hide attribute(s) not being viewed/edited. -->
#Html.HiddenFor(model => model.ResponseUID)
#Html.HiddenFor(model => model.QuestionUID)
<br>
<!-- Display attribute(s) being edited. -->
#Html.EditorFor(model => model.Response, new { htmlAttributes = new { #type = "text", #name = "question", #class = "question_input" } })
#Html.ValidationMessageFor(model => model.Response, "", new { #class = "text-danger" })
<input type="button" name="addRow[]" class="deleteRow" value="Delete">
}
</div>
The problem that I am having is that when I get back to my controller to POST the data inserted by the user, my Dictionary is empty. I'm not sure if I am inserting the information correctly. I am changing the dictionary object toArray(), not sure if this is affecting anything...
Here is the HTTP POST create method:
[HttpPost]
[ValidateAntiForgeryToken]
public ActionResult Create([Bind(Include = "ResponseUID, QuestionUID, Response, NextQuestion")] Dictionary<QUESTION, QUESTION_RESPONSES> question_question_response)
{
if (ModelState.IsValid)
{
foreach (var item in question_question_response.ToArray())
{
db.QUESTION_RESPONSES.Add(item.Value);
db.SaveChanges();
return RedirectToAction("Index"); // Update to take user to Actions/Create page.
}
}
ViewBag.NextQuestion = new SelectList(db.QUESTIONS, "QuestionUID", "Question1");
return View(question_question_response);
}
Open to any suggestions of different ways I could do this or on what may be wrong with what I am currently doing.
Create 2 ViewModels:
public class QuestionAireViewModel {
public int QuestionAireId {get;set;}
public List<QuestionViewModel> Quesitons {get;set;}
}
public class QuestionViewModel{
public int QuestionId {get;set;}
public string Question {get;set;}
public string QuestionResponse {get;set;}
}
In your view pass this QuestionAireViewModel
Generate QuestionAireViewModel in your controller like this:
public ActionResult GetQuestions(int id)
{
var questionAire = db.QuesitonAire.First(s => s.QuestionAireId == id)
var questions = new List<QuestionViewModel>();
foreach(var question in questionAire.Questions){
questions.Add(new QuestionViewModel(){
Quesiton = question.Question,
});
}
var model = new QuestionAireViewModel(){
QuestionAireId = questionAire.Id,
Quesitons = questions
};
return View(model);
}
Then on POST Form method will be:
[HttpPost]
public ActionResult SaveQuestions(QuestionAireViewModel model)
{
}

MVC Ajax with Dynamic Partial View Creation

How can I create dynamic ajax.actionlinks that will call dynamic partial views.
For example:
I have a page that will generate x number of comments
Each comment can be voted up or down (individually)
The number of up votes and down votes are counted into a single integer
Each comment div will have its own ajax.actionlink
Each ajax.actionlink will pass to the controller the ID of the comment
The controller will calculate the total votes and call the partial view to display back into the div with the correct ID.
What have I done so far:
I have been able to create successful ajax.actionlink
That will call a controller and sum the votes
That will call the partial view and display the votes
What is the issue
I don't want to hard code 30-100 different ajax.actionlinks to call 30-100 hard coded partial views.
How can I accomplish this dynamically?
Existing Code:
My ajax.actionlink inside my razor view
#Html.Raw(Ajax.ActionLink("[replacetext]", "VoteUp",
new { UserPostID = #Model.Id },
new AjaxOptions { HttpMethod = "POST", InsertionMode = InsertionMode.Replace, UpdateTargetId = "CountVote" }).ToHtmlString().Replace("[replacetext]",
"<img src=\"/Images/up_32x32.png\" />"))
My div inside the same razor view to display the returning results from the partial view.
<div id="CountVote" class="postvotes"></div>
My controller
public PartialViewResult VoteUp(int UserPostID)
{
try
{
UserVotes vote = new UserVotes();
vote.SubmitedVote = 1;
vote.UserId = Convert.ToInt32(Session["id"]);
vote.UserPostID = UserPostID;
ViewBag.SumVotes = postRepository.InsertUserPostVote(vote);
}
catch (Exception e)
{
xxx.xxx.xxxx().Raise(e);
}
return PartialView("_TotalVotes");
}
And finally my partial view (_TotalVotes.cshtml)
#ViewBag.SumVotes
Now my main view for Viewpost shows the comments in a loop using the viewbag.
foreach (var item in (List<UserComment>)ViewData["Comments"])
{
CommentVote = "cv" + i.ToString();
<div class="postlinewrapper">
<div class="postvotesframe">
<div class="postvotes">
#Html.Raw(Ajax.ActionLink("[replacetext]", "VoteUp",
new AjaxOptions { HttpMethod = "POST", InsertionMode = InsertionMode.Replace, UpdateTargetId = "CountVote" }).ToHtmlString().Replace("[replacetext]",
"<img src=\"/Images/up_32x32.png\" />"))
</div>
<div id="#CommentVote" class="#CommentVote">0</div>
<div class="postvotes">
#Html.Raw(Ajax.ActionLink("[replacetext]", "VoteDown",
new AjaxOptions { HttpMethod = "POST", InsertionMode = InsertionMode.Replace, UpdateTargetId = "CountVote" }).ToHtmlString().Replace("[replacetext]",
"<img src=\"/Images/down_32x32.png\" />"))
</div>
</div>
<div class="postleftbar">
#Html.Raw(item.Comment)
</div>
<div class="postrightbar">
<div>
<div class="post_spec">
<div class="post_spec_title">Call Sign: </div>
<div class="post_spec_detail">#item.CallSign</div>
</div>
<div class="post_spec">
<div class="post_spec_title">When: </div>
<div class="post_spec_detail">#item.CommentDate.ToString("dd/MM/yyyy")</div>
</div>
</div>
<br />
<br />
</div>
</div>
i += 1;
}
I have implemented the login to increase or decrease votes up and down:
public PartialViewResult VoteUp(int userPostId)
{
try
{
UserVotes vote = new UserVotes();
vote.SubmitedVote = 1;
vote.UserId = Convert.ToInt32(Session["id"]);
vote.UserPostID = userPostId;
ViewBag.SumVotes = postRepository.InsertUserPostVote(vote);
}
catch (Exception e)
{
xxxx.xxxx.xxxx().Raise(e);
}
return PartialView("_TotalVotes");
}
public PartialViewResult VoteDown(int userPostId)
{
try
{
UserVotes vote = new UserVotes();
vote.SubmitedVote = -1;
vote.UserId = Convert.ToInt32(Session["id"]);
vote.UserPostID = userPostId;
ViewBag.SumVotes = postRepository.InsertUserPostVote(vote);
}
catch (Exception e)
{
xxx.xxxx.xxxx().Raise(e);
}
return PartialView("_TotalVotes");
}
Now all this code works for 1 ajax call just fine, but what I need to is to display separate ajax calls for separate divs dynamically.
Try it this way.
Main view
I'm supposing you have a model with a collection property Comments of Comment items
#model MyNamespace.CommentAndOtherStuff
<ul>
#foreach(item in Model.Comments)
{
<li>
<a href="#Url.Action("VoteUp", "VoteControllerName", new { UserPostId = item.Id })"
class="vote-link"
data-id="#item.Id">#item.Votes</a><img src="vote.jpg" />
</li>
}
</ul>
And your controller just returns a class called VoteResult as JSON.
[HttpPost]
public ActionResult VoteUp(int UserPostID)
{
...
var model = new VoteResult
{
UserPostID = UserPostID,
Votes = service.tallyVote(UserPostID)
};
return Json(model);
}
Now hook all of those up with a jQuery event handler and setup an AJAX call
$(document).ready(function() {
$("a.vote-link").on("click", function(event) {
event.preventDefault();
var link = $(this); // the link instance that was clicked
var id = link.attr("data-id");
var url = link.attr("href");
$.ajax({
url: url,
type: "post"
})
.done(function(result) {
// JSON result: { UserPostID: 1, Votes: 5 }
// replace link text
link.html(result.Votes);
});
});
});
But I want a partial view html fagment.
[HttpPost]
public ActionResult VoteUp(int UserPostID)
{
...
var model = new VoteResult
{
UserPostID = UserPostID,
Votes = service.tallyVote(UserPostID)
};
return PartialView("_TotalVotes", model);
}
_TotalVotes partial
#model MyNamespace.VoteResult
#if (Model.Votes < 0)
{
<span class="unpopular">#Model.Votes</span>
}
else
{
<span class="awesome">#Model.Votes</span>
}
And adjust the AJAX callback
.done(function(result) {
link.html(result);
});
Now you could write a helper for the link fragment but it obfuscates things in my opinion (it's a judgement call). All you really need here is the class name and the data-id which your javascript will bind.
Using the Ajax helpers here seems an unnecessary overhead and I suggest you just use jquery methods to update the DOM. Your current code suggests you might be missing some logic to make a comment voting system work, including indicating what action the user may have already performed. For example (and assuming you want it to work similar to SO), if a user has previously up-voted, then clicking on the up-vote link should decrement the vote count by 1, but clicking on the down-vote link should decrement the vote count by 2 (the previous up-vote plus the new down-vote).
Refer to this fiddle for how this might be styled and behave when clicking the vote elements
Your view model for a comment might look like
public enum Vote { "None", "Up", "Down" }
public class CommentVM
{
public int ID { get; set; }
public string Text { get; set; }
public Vote CurrentVote { get; set; }
public int TotalVotes { get; set; }
}
and assuming you have a model that contains a collection of comments
public class PostVM
{
public int ID { get; set; }
public string Text { get; set; }
public IEnumerable<CommentVM> Comments { get; set; }
}
and the associated DisplayTemplate
/Views/Shared/DisplayTemplates/CommentVM.cshtml
#model CommentVM
<div class="comment" data-id="#Model.ID" data-currentvote="#Model.CurrentVote">
<div class="vote">
<div class="voteup" class="#(Model.CurrentVote == Vote.Up ? "current" : null)"></div>
<div class="votecount">#Model.TotalVotes</div>
<div class="votedown" class="#(Model.CurrentVote == Vote.Down ? "current" : null)"></div>
</div>
<div class="commenttext">#Html.DisplayFor(m => m.Text)</div>
</div>
Then in the main view
#model PostVM
.... // display some properties of Post?
#Html.DisplayFor(m => m.Comments)
<script>
var voteUpUrl = '#Url.Action("VoteUp")';
var voteDownUrl = '#Url.Action("VoteDown")';
$('.voteup').click(function() {
var container = $(this).closest('.comment');
var id = container.data('id');
var voteCount = new Number(container.find('.votecount').text());
$.post(voteUpUrl, { id: id }, function(response) {
if (!response) {
// oops, something went wrong - display error message?
return;
}
container.find('.votecount').text(response.voteCount); // update vote count
if (response.voteCount < voteCount) {
// the user previously upvoted and has now removed it
container.find('.voteup').removeClass('current');
} else if (response.voteCount == voteCount + 1) {
// the user had not previously voted on this comment
container.find('.voteup').addClass('current');
} else if (response.voteCount == voteCount + 2) {
// the user previoulsy down voted
container.find('.votedown').removeClass('current');
container.find('.voteup').addClass('current');
}
});
});
$('.votedown').click(function() {
... // similar to above (modify logic in if/elseif blocks)
});
</script>
and the controller method
public JsonResult VoteUp(int id)
{
int voteCount = // your logic to calculate the new total based on the users current vote (if any) for the comment
return Json(new { voteCount = voteCount });
}

Pass ViewBag to _Layout in MVC?

In my Search controller I have:
public JsonResult Search(string term)
{
var termLower=term.ToLower();
var pictures=_PictureRepo.GetAll();
var productsWereSeached = _ProductRepo.GetAll().Where(x => x.Name.ToLower().Contains(term)).Select(x=> new ProductData
{
Name=x.Name,
Price=x.Price,
Id=x.Id,
Warranty=x.Warranty,
Picture=x.Pictures.FirstOrDefault()
});
ViewBag.NOfMatchedProduct = productsWereSeached.Count();
productsWereSeached = productsWereSeached.Take(2);
foreach (var product in productsWereSeached)
{
product.Picture = _PictureRepo.GetAll().Where(x => x.ProductId == product.Id).FirstOrDefault();
}
return Json(productsWereSeached);
}
In my _Layout I have :
<div>
<input id="nOfMatchedProducts" value='#ViewBag.NOfMatchedProduct'/>
<ul id="realPlaceForSearchItems">
</ul>
</div>
Maybe I should put this code from _Layout to PartialView. The question would be, how to pass ViewBag data from controller to PartialView.
"public JsonResult Search" - so you are already calling your method in ajax, right?
Yes, but I am returning 2 productsWereSeached
You already have the result and just need to show it to your targeted element:
$.get('#Url.Action("Search")',function(result){
$("#nOfMatchedProducts").html(result.length);
});
If you want to return additional information like the total number of records searched in addition to "your filtered results", then you can pass it like this:
var totalrows = productsWereSeached.Count();
//filter your search (productsWereSeached)
return Json(new {list=productsWereSeached, totalrows });
Then do this ajax call:
$.get('#Url.Action("Search")',function(result){
$("#nOfMatchedProducts").html(result.totalrows);
});