MVC Using HtmlHelper method LaberFor(), EditorFor() in controller - c#

I have a View displaying a list of items. I want to display an existing item when user select one to "edit", or display a new item when user clicks "create". This "single item" window will be called in many different places so I want combine the function "Edit" and "Create" into one view, which is not too bad. I understand I can do this by creating a view page on the same level of the caller, and when user clicks "submit" on this item window, the item controller do its job and then redirect back to the caller page (item list or other caller). But I want to try another approach, which is make this Item window as a pop up window, when user clicks on the "show modal" button, the caller page makes an ajax call to controller, passing item's data (or its id) as parameter, and the controller generate the MvcHtmlString dynamically, return to the caller, and caller display the popup window using the MvcHtmlString received. Everything seems to working, but the massive html code in my controller looks pretty messy.
So I'm wonder how to use HtmlHelper to generate MvcHtmlString in controller, for example, I have a object type "item", how can I do things like Html.LabelFor(item => item.Name), or Html.EditorFor(item => item.Name) in controller and get the MvcHtmlString?

You can extend your Htmlhelpers. If you want to just achieve edit/create button and you can change your viewmodel for condition or maybe you just want to use same model. You can use like this extension that i'm currently using on my views.
public static MvcHtmlString CustomActionLink(this HtmlHelper html, string action, string controller,
string displayText, bool isCreate)
{
if (isCreate)
{
var targetUrl = UrlHelper.GenerateUrl("Default", action, controller,
null, RouteTable.Routes, html.ViewContext.RequestContext, false);
var anchorBuilder = new TagBuilder("a");
anchorBuilder.MergeAttribute("href", targetUrl);
string classes = "btn btn-progress";
anchorBuilder.MergeAttribute("class", classes);
//Return as MVC string
anchorBuilder.InnerHtml = displayText;
return new MvcHtmlString(anchorBuilder.ToString(TagRenderMode.Normal));
}
else
{
var spanBuilder = new TagBuilder("span");
spanBuilder.MergeAttribute("class", "btn btn-progress");
spanBuilder.InnerHtml = displayText;
return new MvcHtmlString(spanBuilder.ToString(TagRenderMode.Normal));
}
}

Related

ViewDataDictionary vs anonymous object in ASP.net MVC 5

I am a beginner and I am going through some tutorials in my MVC. So, I came across two scenarios.
Scenario 1.
I had to pass some data to my view on post and then send that data as hidden field. Here is the code.
[HttpPost]
[ValidateAntiForgeryToken]
public ActionResult ForgotPassword(ForgotPasswordMV viewModel)
{
if (ModelState.IsValid)
{
return RedirectToAction("VerifyToken", new { emailId = viewModel.EmailId });
}
^^ USING ANONYMOUS OBJECTS
return View();
}
public ActionResult VerifyToken(string emailId = null)
{
VerifyTokenMV viewModel = new VerifyTokenMV
{
EmailId = emailId
};
return View(viewModel);
}
VerifyToken View
#using (#Html.BeginForm("VerifyToken", "Security"))
{
#Html.HiddenFor(m => m.EmailId)
<button class="btn btn-primary">Continue</button>
}
Works Perfectly fine. I am able to receive values of EmailId. So far so good.
Scenario 2.
Needed to open a partial view from Main view, here is the snippet.
Main cshtml file
<div class="abc">
#Html.Partial("../Widget/Customize", Model.Unit, new ViewDataDictionary() { { "ElementName", "UnitWidget" } })
</div>
partial cshtml file
#{
string custWidgetElementName = ViewBag.ElementName;
}
// some html code below
Observation:
In scenario 2 why have I used ViewDataDictionary. Although both example works perfectly fine. But is there any reason that I had to use ViewDataDictionary. In scenraio 1 can we use ViewDataDictionary? If Yes, then which one is optimum solution.
Question: When I need to pass values shall I use new {key : value} or use ViewDataDictionary or there is no corelation? Instead of ViewDataDictionary can I use anonymous object in Senario 2
Your two scenarios are totally different. They are not doing the same thing.
In scenario 1 when using this line:
return RedirectToAction("VerifyToken", new { emailId = viewModel.EmailId });
A new URL is genrated and sent back to the client (the browser) with HTTP Code 301 or 302. When received the browser will re-contact your application wiht the received URL. With that URL, your application will execute the associated action. In your case, the client's browser will call VerifyToken action with the emailId parameter setted when you call RedirectionToAction into ForgotPassword action. So using RedirectionToAction method is just telling that method to generate a new URL with parameter defined in the anonymous type.
In scenario 2 is completely different to scenario 1. With this line:
#Html.Partial("../Widget/Customize", Model.Unit, new ViewDataDictionary() { { "ElementName", "UnitWidget" } })
You're telling your view to inject the partial view which path is ../Widget/Customize. Because that partial view the strongly typed, you passed Model.Unit as an second parameter. You use also a third parameter new ViewDataDictionary() { { "ElementName", "UnitWidget" } } because your partial seems to internally need to access to the dynamic property ViewBag or dictionary property ViewData of your view.
Conclusion:
In scenario 1 you are just telling the client's browser to go to the new URL you have generated after requesting ForgetPassword URL. We just call that a rediretion.
In scenario 2, you're just rendering a partial view into a view. The client's broswer doesn't know anything what's going on with partial views they don't know if they exist.

MVC Back Button Having Problems

I want to have a "Back" button that returns a user back to a page with a grid/table on it, but I'm having some trouble. The point of the back button is to allow my user's grid settings to be persisted between viewing records. The link that the Back button points to depends on what URL the user comes from. The url is set in the controller method. When the Url looks like the following...
http://localhost:49933/OpenOrder
... there is very little problem getting the back button to work. However, when the URL looks like this...
http://localhost:49933/OpenOrder?grid-sort=&grid-page=1&grid-pageSize=1000&grid-group=&grid-filter=CustomerName~contains~'TEST'
... sometimes the "Back" button won't work at all. I'm using a Telerik Kendo MVC grid, and this is what happens to the URL when a user filters the page.
I have noticed that the following code in the view works consistently on all links when there is no form tag (e.g. #Html.BeginForm())...
View
<a href='#Url.Content(#ViewBag.PreviousReferrer)' style="display:none;">
<input type="button" value="Back" />
</a>
But this doesn't work well with Views that do have the form tag. All I get is a Back button that I can click, but does absolutely nothing. It doesn't even show up in Fiddler.
Other things that I've tried include...
<input type="button" value="Back" onclick="#("window.location.href='" + #Url.Content(ViewBag.PreviousReferrer) + "'");" />
<input type="button" value="Back2" onclick="window.location.href = '#Url.Content(#ViewBag.PreviousReferrer)'" />
<input type="button" value="Back3" onclick="javascript:window.location=('#Url.Content(#ViewBag.PreviousReferrer)')" />
As stated previously, the ViewBag.PreviousReferrer property is set in the controller. Depending on whether a user is getting or posting, it will use either the Request.PreviousReferrer, or a session variable that I set. I won't include all my controller code since the rest of the pages work fine, but here's what it looks like when I set the ViewBag.PreviousReferrer in the gets and posts. At least one GET will come before any posts, so the session variable will always be set to something.
Controller
GET
string urlReferrer = Request.UrlReferrer == null ? string.Empty : Request.UrlReferrer.AbsoluteUri.ToUpper();
if (!string.IsNullOrEmpty(urlReferrer) && urlReferrer.Contains("OPENORDER"))
{
ViewBag.PreviousReferrer = Request.UrlReferrer.AbsoluteUri;
Session["OpenOrderPreviousReferrer"] = Request.UrlReferrer.AbsoluteUri;
}
else
{
ViewBag.PreviousReferrer = "~/OpenOrder";
}
POST
ViewBag.PreviousReferrer = Session["OpenOrderPreviousReferrer"] ?? "~/OpenOrder";
Ideally, this is what I want: One style of markup to go to the link in the PreviousReferrer REGARDLESS of whether or not the button is inside or outside of a form tag.
I think the problem might have something to do with HTML escape sequences, but I don't know if that's the technical term for it.
I had to create button links, that did a browser back "a lot" in one project, so created this HtmlHelper extension:
public static class ActionLinkButtonHelper
{
/// <summary>
/// Add a back button that generates a Javascript browser-back
/// </summary>
/// <param name="htmlHelper">HtmlHelper we are extending</param>
/// <param name="buttonText">Text to display on back button - defaults to "Back"</param>
/// <param name="actionName">Name of action to execute on click</param>
/// <param name="controller">Name of optional controller to send action to</param>
/// <returns></returns>
public static MvcHtmlString BackButton(this HtmlHelper htmlHelper, string buttonText="Back", string actionName="index", string controller=null, object routeValuesObject = null)
{
// Note: "Index is provided as a default
return ActionLinkButton(htmlHelper, buttonText, actionName, controller, routeValuesObject, new { onclick = "history.go(-1);return false;" });
}
}
and use it in the page like this:
#Html.BackButton()
or this:
#Html.BackButton("Button text", "action", "controller", new {id=routevalues})
It generates a button with a small snippet of JS that causes the browser to go back, so an actual link is not required unless JavaScript is disabled.
Supporting method (ActionLink as a button):
public static MvcHtmlString ActionLinkButton(this HtmlHelper htmlHelper, string buttonText, string actionName, string controllerName, object routeValuesObject = null, object htmlAttributes = null)
{
// For testing - create links instead of buttons
//return System.Web.Mvc.Html.LinkExtensions.ActionLink(htmlHelper, buttonText, actionName, controllerName, routeValues, htmlAttributes);
if (string.IsNullOrEmpty(controllerName))
{
controllerName = HttpContext.Current.Request.RequestContext.RouteData.Values["controller"].ToString();
}
RouteValueDictionary routeValuesDictionary = new RouteValueDictionary(routeValuesObject);
RouteValueDictionary htmlAttr = HtmlHelper.AnonymousObjectToHtmlAttributes(htmlAttributes);
TagBuilder tb = new TagBuilder("input");
tb.MergeAttributes(htmlAttr, false);
string href = UrlHelper.GenerateUrl("default", actionName, controllerName, routeValuesDictionary, RouteTable.Routes, htmlHelper.ViewContext.RequestContext, false);
tb.MergeAttribute("type", "button");
tb.MergeAttribute("value", buttonText);
if (!tb.Attributes.ContainsKey("onclick"))
{
tb.MergeAttribute("onclick", "location.href=\'" + href + "\';return false;");
}
return new MvcHtmlString(tb.ToString(TagRenderMode.Normal).Replace("'", "\'").Replace(" "," "));
}
Views/Web.config change:
To make the above available to Intellisense/auto-complete in Views you need to add a new entry in the Views/web.config file <namespaces> like this:
<namespaces>
... [SNIP] ...
<add namespace="My.Lib.Name" />
</namespaces>
Where My.Lib.Name is the namespace where you have placed the ActionLinkButtonHelper shown above.

How can i get the actual controller in post method

I have two controllers Project and Tag, both of which have a create view and get post methods.
From the Project create view I have the option to add a tag which opens a dialog with the tag create view.
When i add the tag it goes to the tag controller create post method at which point i want to be able to get the controller action that sent it there (in this case Project). I have seen the UrlReferer class, is there a better way to get the controller than that?
the reason i need this is i want to be able to do something like
if (Request.IsAjaxRequest())
{
if (REFERER CONTROLLER != Tag Controller)
{
return Json(new { Item = item, Success = true });
}
else
{
return RedirectToAction("Index");
}
}
so basically if the dialog is in another controller then return a json of the new value otherwise return the index action
Edit ended up using this idea again. went for
if (Url.IsLocalUrl(Request.UrlReferrer.AbsoluteUri) && !String.Equals(Request.UrlReferrer.LocalPath.TrimEnd('/'), Url.Action("Index"), StringComparison.OrdinalIgnoreCase))
{
return Json(new { Item = item, Success = true, Field = String.Format("#Selected{0}s", ControllerName) });
}
return Json(new { Success = true, Field = "#mainContent", Url = Url.Action("Index") });
You have a few options:
You can look at the referring URL (there's no point in taking the referring URL string, parsing out the controller name and then creating an instance of your controller class unless you need access to some sort of method or property in the class; I would just look at the string).
You can include a hidden input which includes the controller name.
You could store a value in session (this seems like overkill; remember, a cookie will be created for this) to remember what page the user came from.
Options 1 and 2 could be tampered with before your server receives the value.

ASP.NET MVC2 call a service method from the master page?

In my MVC2 app that uses the Service - Repository pattern, how can I call a service method from the master page?
+--------------------------------------+
| Logo Welcome xyz|
+--------------------------------------+
| Home | Sales | Import | Admin (menu) |
+--------------------------------------+
In my menu I now have some pages that have restricted access by user role. I have an existing service method that can check if the current user can view a certain page or not:
IPageAccessService.CanAccess(int pageId, int roleId);
On the controller methods I can call this to check if the user can see the page or not:
public ActionResult Update(int id?)
{
if (!_pageAccessService.CanAccess(pageId, roleId))
{
return RedirectToAction("Index", "Home");
}
}
But I don't know how to call this method from my Site.Master so that when it creates the menu it does not show the menu item if the user does not have access (the menu is a simple unordered list):
<li>Admin
<ul>
<li>User Roles</li>
<li>Admin Reports</li>
</ul>
</li>
I'm guessing that it would need look something like this (have to check each page before adding to the list):
if (_pageAccessService.CanAccess(pageId, roleId)) <li>Admin
<ul>
if (_pageAccessService.CanAccess(pageId, roleId)) <li>User Roles</li>
if (_pageAccessService.CanAccess(pageId, roleId)) <li>Admin Reports</li>
</ul>
</li>
But before I can do that I need to know how to actually call a service method from the master.
EDIT:
I've adapted Darin's answer and have this:
public static class LinkExtensions
{
private static readonly IPageAccessRepository _repo = new PageAccessRepository();
private static readonly IPageAccessService _pageAccess = new PageAccessService(_repo);
public static MvcHtmlString MenuItem(
this HtmlHelper htmlHelper, string linkText,
string url, string pageName
)
{
if (!_pageAccess.CanAccess(pageName))
{
return MvcHtmlString.Empty;
}
// The user can access the page => show the menu
var a = new TagBuilder("a");
a.Attributes["href"] = url;
a.SetInnerText(linkText);
return MvcHtmlString.Create(string.Format("<li>{0}</li>",a));
}
The problem is that I still need to call the service, so I need to be able to instantiate it. Because its a static class, my IoC container won't help here. So I still have to manually do create the service and repository. And it's still got the same problem as my original ugly workaround - manually creating a repository in a view.
You could write a custom HTML helper rendering the different items of this menu. Inside the helper based on the user roles you will decide whether to generate or not the given item. For example something among the lines:
public static class LinkExtensions
{
public static MvcHtmlString MenuItem(
this HtmlHelper htmlHelper,
string linkText,
string url,
string requiredRole
)
{
var a = new TagBuilder("a");
a.Attributes["href"] = url;
a.SetInnerText(linkText);
if (string.IsNullOrEmpty(requiredRole))
{
// No role required => show the menu item
return MvcHtmlString.Create(a.ToString());
}
var user = htmlHelper.ViewContext.HttpContext.User;
if (!user.IsInRole(requiredRole))
{
// A role is required but no user authenticated or user is not in role
// => show empty
return MvcHtmlString.Empty;
}
// The user is in role => show the menu
return MvcHtmlString.Create(a.ToString());
}
}
and inside the view:
<li>
<%= Html.MenuItem("Admin", Url.Content("~/Admin"), "admin") %>
<ul>
<li>
<%= Html.MenuItem("User Roles", Url.Content("~/Admin/Roles"), "userroles") %>
</li>
<li>
<%= Html.MenuItem("Admin Reports", Url.Content("~/Admin/AdminReports"), "admin") %>
</li>
</ul>
</li>
Another possibility is to use child actions and the Html.Action helper inside the master.
Anything that requires logic should not be in Views, including their helpers of course.
I'd suggest for this you add a new Controller Action, add your logic to the action, and return the username directly from the controller or if you want to display more stuff like login controls whatever you can use a view.
So, create LoginWelcomeMessage class with Username and IsLoggedIn properties for example. In the action set those based on the whatever checks you want, and send them to the view which shows/hides based on them.
In your masterpage, you execute #Html.RenderAction() to execute the action that brings the username and so on.
The same idea can be done for the entire menu. IT's up to you whether to have the whole header in one Action / View or have two different ones. If you have the same view then the model can have extra properties like CanViewSalesMenuItems, CanViewAdminMenuItems and so on, that are set from your action method, and used from its view to display/hide items.
The beauty is that the masterpage won't care, it just delegates the whole thing to the controller action and its view using RenderAction()

MVC 3 - How do you return a Display Template from an action method?

I have a view that displays a list of comments. It does this via the DisplayTemplate. All I have to do is something like #Html.DisplayFor(x => x.BlogPost.PostComments) and all the comments render appropriately.
There is a form at the bottom of the page to add a new comment. This page utilizes progressive enhancement. So if javascript is disabled then the form submits like normal, adds the comment to the database, then redirects to the action that renders the blog post. However, if javascript is available then jQuery hijacks the form's submit and makes the post via ajax. Well because the comment markup is in a display template, I don't know how to return it from the action method so that jQuery can drop it on the page.
I know how to do this with partial views. I would just have the action method return the right partial view and jquery would append the response to the comment container on the page.
Before I go chopping out my display template in favor of a partial view, is there a straight forward way that I'm missing to send back a display template from the controller?
Here is my action method:
public ActionResult AddComment(PostComment postComment)
{
postComment.PostedDate = DateTime.Now;
postCommentRepo.AddPostComment(postComment);
postCommentRepo.SaveChanges();
if (Request.IsAjaxRequest())
return ???????
else
return RedirectToAction("BlogPost", new { Id = postComment.BlogPostID });
}
When the page loads it doesn't need to worry about it because it uses the templates in the standard way:
<div class="comments">
#Html.DisplayFor(x => x.BlogPost.BlogPostComments)
</div>
I just want to know how I might send a single comment that utilizes the display template back to jQuery.
You may try returning the partial HTML representing the newly posted comment:
if (Request.IsAjaxRequest())
{
return PartialView(
"~/Views/Shared/DisplayTemplates/Comment.cshtml",
postComment
);
}
and on the client side append this comment to the comments container:
$.post('#Url.Action("AddComment")', { ... }, function (result) {
$('#comments').append(result);
// or $('#comments').prepend(result); if you want it to appear on top
});
Does this question give you what you are looking for? Seems to indicate that you can call a HTML helper from an action.
Create a partial view /Shared/DisplayTemplate.cshtml with the following razor code:
#Html.DisplayFor(m => Model)
Then in your controller (or preferably in a base controller class) add a method along these lines:
protected PartialViewResult PartialViewFor(object model)
{
return PartialView("DisplayTemplate",model);
}
In the OP's case then:
public ActionResult AddComment(PostComment postComment)
{
postComment.PostedDate = DateTime.Now;
postCommentRepo.AddPostComment(postComment);
postCommentRepo.SaveChanges();
if (Request.IsAjaxRequest())
return PartialViewFor(postComment);
else
return RedirectToAction("BlogPost", new { Id = postComment.BlogPostID });
}

Categories