Retaining RouteData in Html.BeginForm() - asp.net

I have been using a variant of the Html.BeginForm() method to attach an html attribute to my form, like this :
#using (Html.BeginForm("actionname", "controllername", FormMethod.Post, new { id = "myform" }))
Unfortunately this causes the form target to loose all route data.
Say my url was controller/action?abc=123, then using Html.BeginForm() generates the form post target as controller/action?abc=123 but the overloaded version (which I am using to add the html id attribute to the form), generates the target as controller/action (which actually is understandable, since I am specifying the route myself, but it doesn't solve my purpose).
Is there a variant of the Html.BeginForm() which would allow me retain the old route values and let me add html attributes to the form at the same time?

As far as I can see, only the parameterless version of BeginForm uses the current full URL.
public static MvcForm BeginForm(this HtmlHelper htmlHelper) {
// generates <form action="{current url}" method="post">...</form>
string formAction = htmlHelper.ViewContext.HttpContext.Request.RawUrl;
return FormHelper(htmlHelper, formAction, FormMethod.Post, new RouteValueDictionary());
}
I'm not sure if this is the best way, but you could write a custom form helper to include the QueryString values:
public static class MyFormExtensions
{
public static MvcForm MyBeginForm(this HtmlHelper htmlHelper, object htmlAttributes)
{
var rvd = new RouteValueDictionary(htmlHelper.ViewContext.RouteData.Values);
var queryString = htmlHelper.ViewContext.HttpContext.Request.QueryString;
foreach (string key in queryString.AllKeys) rvd.Add(key, queryString[key]);
return htmlHelper.BeginForm(null, null, rvd, FormMethod.Post, HtmlHelper.AnonymousObjectToHtmlAttributes(htmlAttributes));
}
}
#using (Html.MyBeginForm(new { id = "myform" }))
{
//...
}

Related

ASP.net partial view relative action links

Is there a way to have action links which are relative to the current view.
For example, lets say I have a partial view which is a contains a paged list of news articles called _ArticlesList. I want to include this in the Admin and Index views, which are controlled by their relative controllers. _ArticlesList produces URLs which have the routeValues pageNumber and pageSize, but you have to hard code the controller, don't you?
I think what I want to do is just override properties in the routeValue object?
Edit:
I guess I could use HttpContext.Current.Request.RequestContext.RouteData.Values["controller"].ToString()
but that looks pretty bad
If you want to upload in For EX. Index file then write following to implement in your cshtml.
<div>
#Html.Partial("_ArticleList", Model)
</div>
You can put this in any div tag.
I used this to achieve what I wanted
public static class UrlHelperExtensions
{
public static string RelativeAction(this UrlHelper url, object routeValues)
{
var routeDataValues = url.RequestContext.RouteData.Values;
var queryString = url.RequestContext.HttpContext.Request.QueryString;
foreach (string key in queryString.Keys)
{
routeDataValues[key] = queryString[key];
}
// Allow routeValue object to take precedence over queryString
foreach (var prop in new RouteValueDictionary(routeValues))
{
routeDataValues[prop.Key] = prop.Value;
}
return url.RouteUrl(routeDataValues);
}
}

Dropdownlist returning null viewdata Id in partialview

I am working on a multi-step form which must commit the first step to database before the other steps add up and commit finally after last step. Looking at the options I decided to use sessions(however I will be happy to use any better alternative). I managed to get this to work and later decided to use ajax for the form submission hence the requirement of partialviews. The problem is the dropdowns are returning null viewdata values - ie they are not binding. Can anyone suggest the best way to compose in the controller to make this work. It works fine if I revert to return views instead of return partialviews.
Controller
[AllowAnonymous]
public ActionResult ContactViewDetails()
{
ViewBag.CountryList = new SelectList(db.Countries, "CountryId", "CountryName");
return PartialView("ContactViewDetails");
}
model
public int CountryId{get;set;}
public virtual Country Country { get; set; }
.......and others
Default Page View
<script src="~/Scripts/jquery-2.1.3.min.js"></script>
<script src="~/Scripts/jquery.unobtrusive-ajax.js"></script>
<div id="divContainer">
#Html.Partial("RegViewDetails")
</div>
PartialView: ContactViewDetails.cs
#{
AjaxOptions options = new AjaxOptions();
options.HttpMethod = "POST";
options.InsertionMode = InsertionMode.Replace;
options.UpdateTargetId = "divContainer";
}
#using (Ajax.BeginForm("ContactViewDetails", "OnlineApplication", options, new { #class = "form-horizontal" }))
{
#Html.DropDownListFor(x => x.CountryId, (IEnumerable<SelectListItem>)ViewBag.CountryList, new { #class = "chooseOption" })
#Html.......others
}
The ContactViewDetails page is the second step in the form succession The first step is RegViewDetails page as you can see in the Default page view. After validation RegViewDetails returns ContactViewDetails Partial
......
return PartialView("ContactViewDetails");

Asp.Net MVC Dynamic Model Binding Prefix

Is there any way of changing the binding prefix with a value which comes from the request parameters?
I have many nested search popups, and all of them shares the same ViewModel.
I can add a binding prefix to all the fields when requesting for the Search filters, but i don't know how can i make the [Bind(Prefix = "")] to work with values coming from the request parameters.
// get the search filters with the bindingPrefix we need
public ActionResult Search(string bindingPrefix)
{
ViewData.TemplateInfo.HtmlFieldPrefix = bindingPrefix;
SearchViewModel model = new SearchViewModel
{
BindingPrefix = bindingPrefix
};
return PartialView("_SearchFilters", model);
}
// post the search filters values
[HttpPost]
public ActionResult Search([Bind(Prefix = model.BindingPrefix)]SearchViewModel model)
{
}
I don't know why you would want to do this, but this should work.
In your form on the view, have a hidden value
#Html.Hidden("BindingPrefix", Model.BindingPrefix)
Modify your action to the following
[HttpPost]
public ActionResult Search(SearchViewModel model)
{
UpdateModel(model, model.BindingPrefix);
}

Accessing Route Value of Id inside a Partial View

I am trying to access the Route value of id from inside the PartialView. I have a Post, Comments scenario. And I want my Comments partialview to have access to the PostId.
The Url looks like the following:
Posts/Detail/2
Now, how can I access the value 2 inside the Partial View?
UPDATE:
I changed my CommentsController to the following:
public ActionResult Add(string id,string subject,string name,string body)
{
}
But now when I debug it does not even step into the Add method.
UPDATE 2:
My partial view looks like the following:
<div class="title">Add Comment</div>
#using (Html.BeginForm("Add","Comments",FormMethod.Post))
{
#Html.Label("Subject")
#Html.TextBox("subject")
<br />
#Html.Label("Name")
#Html.TextBox("name")
<br />
#Html.Label("Body")
#Html.TextBox("body")
<input type="submit" value="submit" />
}
And here is the Controller:
public ActionResult Add(string id, string subject, string name, string body)
If I remove id from the above controller action then it works.
You could fetch it from RouteData:
In WebForms:
<%
var id = ViewContext.RouteData.Values["id"];
%>
In Razor:
#{
var id = ViewContext.RouteData.Values["id"];
}
After showing your code it seems that in your form you are not passing the id. So modify it like this:
#using (Html.BeginForm("Add", "Comments", new { id = ViewContext.RouteData.Values["id"] }, FormMethod.Post))
{
...
}
or use a hidden field if you prefer:
#using (Html.BeginForm("Add", "Comments", FormMethod.Post))
{
#Html.Hidden("id", ViewContext.RouteData.Values["id"])
...
}
Now you should get the id in your Add controller action.
Inside of your controller, you could put the route data in the view model that gets sent to the view; the view can then pass along its view model (or portions of it) to the partial view. Your views, including partial views, should ideally only need to rely on data provided by their view models to render.
public ActionResult Add(string id, string subject, string name, string body)
{
return View(
new AddedViewModel {
Id = id,
// etc...
}
);
}

How to include a partial view inside a webform

Some site I'm programming is using both ASP.NET MVC and WebForms.
I have a partial view and I want to include this inside a webform. The partial view has some code that has to be processed in the server, so using Response.WriteFile don't work.
It should work with javascript disabled.
How can I do this?
I had a look at the MVC source to see if I could figure out how to do this. There seems to be very close coupling between controller context, views, view data, routing data and the html render methods.
Basically in order to make this happen you need to create all of these extra elements. Some of them are relatively simple (such as the view data) but some are a bit more complex - for instance the routing data will consider the current WebForms page to be ignored.
The big problem appears to be the HttpContext - MVC pages rely on a HttpContextBase (rather than HttpContext like WebForms do) and while both implement IServiceProvider they're not related. The designers of MVC made a deliberate decision not to change the legacy WebForms to use the new context base, however they did provide a wrapper.
This works and lets you add a partial view to a WebForm:
public class WebFormController : Controller { }
public static class WebFormMVCUtil
{
public static void RenderPartial( string partialName, object model )
{
//get a wrapper for the legacy WebForm context
var httpCtx = new HttpContextWrapper( System.Web.HttpContext.Current );
//create a mock route that points to the empty controller
var rt = new RouteData();
rt.Values.Add( "controller", "WebFormController" );
//create a controller context for the route and http context
var ctx = new ControllerContext(
new RequestContext( httpCtx, rt ), new WebFormController() );
//find the partial view using the viewengine
var view = ViewEngines.Engines.FindPartialView( ctx, partialName ).View;
//create a view context and assign the model
var vctx = new ViewContext( ctx, view,
new ViewDataDictionary { Model = model },
new TempDataDictionary() );
//render the partial view
view.Render( vctx, System.Web.HttpContext.Current.Response.Output );
}
}
Then in your WebForm you can do this:
<% WebFormMVCUtil.RenderPartial( "ViewName", this.GetModel() ); %>
It took a while, but I've found a great solution. Keith's solution works for a lot of people, but in certain situations it's not the best, because sometimes you want your application to go through the process of the controller for rendering the view, and Keith's solution just renders the view with a given model I'm presenting here a new solution that will run the normal process.
General Steps:
Create a Utility class
Create a Dummy Controller with a dummy view
In your aspx or master page, call the utility method to render partial passing the Controller, view and if you need, the model to render (as an object),
Let's check it closely in this example
1) Create a Class called MVCUtility and create the following methods:
//Render a partial view, like Keith's solution
private static void RenderPartial(string partialViewName, object model)
{
HttpContextBase httpContextBase = new HttpContextWrapper(HttpContext.Current);
RouteData routeData = new RouteData();
routeData.Values.Add("controller", "Dummy");
ControllerContext controllerContext = new ControllerContext(new RequestContext(httpContextBase, routeData), new DummyController());
IView view = FindPartialView(controllerContext, partialViewName);
ViewContext viewContext = new ViewContext(controllerContext, view, new ViewDataDictionary { Model = model }, new TempDataDictionary(), httpContextBase.Response.Output);
view.Render(viewContext, httpContextBase.Response.Output);
}
//Find the view, if not throw an exception
private static IView FindPartialView(ControllerContext controllerContext, string partialViewName)
{
ViewEngineResult result = ViewEngines.Engines.FindPartialView(controllerContext, partialViewName);
if (result.View != null)
{
return result.View;
}
StringBuilder locationsText = new StringBuilder();
foreach (string location in result.SearchedLocations)
{
locationsText.AppendLine();
locationsText.Append(location);
}
throw new InvalidOperationException(String.Format("Partial view {0} not found. Locations Searched: {1}", partialViewName, locationsText));
}
//Here the method that will be called from MasterPage or Aspx
public static void RenderAction(string controllerName, string actionName, object routeValues)
{
RenderPartial("PartialRender", new RenderActionViewModel() { ControllerName = controllerName, ActionName = actionName, RouteValues = routeValues });
}
Create a class for passing the parameters, I will call here RendeActionViewModel (you can create in the same file of the MvcUtility Class)
public class RenderActionViewModel
{
public string ControllerName { get; set; }
public string ActionName { get; set; }
public object RouteValues { get; set; }
}
2) Now create a Controller named DummyController
//Here the Dummy controller with Dummy view
public class DummyController : Controller
{
public ActionResult PartialRender()
{
return PartialView();
}
}
Create a Dummy view called PartialRender.cshtml (razor view) for the DummyController with the following content, note that it will perform another Render Action using the Html helper.
#model Portal.MVC.MvcUtility.RenderActionViewModel
#{Html.RenderAction(Model.ActionName, Model.ControllerName, Model.RouteValues);}
3) Now just put this in your MasterPage or aspx file, to partial render a view that you want. Note that this is a great answer when you have multiple razor's views that you want to mix with your MasterPage or aspx pages. (supposing we have a PartialView called Login for the Controller Home).
<% MyApplication.MvcUtility.RenderAction("Home", "Login", new { }); %>
or if you have a model for passing into the Action
<% MyApplication.MvcUtility.RenderAction("Home", "Login", new { Name="Daniel", Age = 30 }); %>
This solution is great, doesn't use ajax call, which will not cause a delayed render for the nested views, it doesn't make a new WebRequest so it will not bring you a new session, and it will process the method for retrieving the ActionResult for the view you want, it works without passing any model
Thanks to Using MVC RenderAction within a Webform
most obvious way would be via AJAX
something like this (using jQuery)
<div id="mvcpartial"></div>
<script type="text/javascript">
$(document).load(function () {
$.ajax(
{
type: "GET",
url : "urltoyourmvcaction",
success : function (msg) { $("#mvcpartial").html(msg); }
});
});
</script>
This is great, thanks!
I'm using MVC 2 on .NET 4, which requires a TextWriter gets passed into the ViewContext, so you have to pass in httpContextWrapper.Response.Output as shown below.
public static void RenderPartial(String partialName, Object model)
{
// get a wrapper for the legacy WebForm context
var httpContextWrapper = new HttpContextWrapper(HttpContext.Current);
// create a mock route that points to the empty controller
var routeData = new RouteData();
routeData.Values.Add(_controller, _webFormController);
// create a controller context for the route and http context
var controllerContext = new ControllerContext(new RequestContext(httpContextWrapper, routeData), new WebFormController());
// find the partial view using the viewengine
var view = ViewEngines.Engines.FindPartialView(controllerContext, partialName).View as WebFormView;
// create a view context and assign the model
var viewContext = new ViewContext(controllerContext, view, new ViewDataDictionary { Model = model }, new TempDataDictionary(), httpContextWrapper.Response.Output);
// render the partial view
view.Render(viewContext, httpContextWrapper.Response.Output);
}
Here's a similar approach that has been working for me. The strategy is to render the partial view to a string, then output that in the WebForm page.
public class TemplateHelper
{
/// <summary>
/// Render a Partial View (MVC User Control, .ascx) to a string using the given ViewData.
/// http://www.joeyb.org/blog/2010/01/23/aspnet-mvc-2-render-template-to-string
/// </summary>
/// <param name="controlName"></param>
/// <param name="viewData"></param>
/// <returns></returns>
public static string RenderPartialToString(string controlName, object viewData)
{
ViewDataDictionary vd = new ViewDataDictionary(viewData);
ViewPage vp = new ViewPage { ViewData = vd};
Control control = vp.LoadControl(controlName);
vp.Controls.Add(control);
StringBuilder sb = new StringBuilder();
using (StringWriter sw = new StringWriter(sb))
{
using (HtmlTextWriter tw = new HtmlTextWriter(sw))
{
vp.RenderControl(tw);
}
}
return sb.ToString();
}
}
In the page codebehind, you can do
public partial class TestPartial : System.Web.UI.Page
{
public string NavigationBarContent
{
get;
set;
}
protected void Page_Load(object sender, EventArgs e)
{
NavigationVM oVM = new NavigationVM();
NavigationBarContent = TemplateHelper.RenderPartialToString("~/Views/Shared/NavigationBar.ascx", oVM);
}
}
and in the page you'll have access to the rendered content
<%= NavigationBarContent %>
Hope that helps!
This solution takes a different approach. It defines a System.Web.UI.UserControl which can be place on any Web Form and be configured to display the content from any URL…including an MVC partial view.
This approach is similar to an AJAX call for HTML in that parameters (if any) are given via the URL query string.
First, define a user control in 2 files:
/controls/PartialViewControl.ascx file
<%# Control Language="C#"
AutoEventWireup="true"
CodeFile="PartialViewControl.ascx.cs"
Inherits="PartialViewControl" %>
/controls/PartialViewControl.ascx.cs:
public partial class PartialViewControl : System.Web.UI.UserControl {
[Browsable(true),
Category("Configutation"),
Description("Specifies an absolute or relative path to the content to display.")]
public string contentUrl { get; set; }
protected override void Render(HtmlTextWriter writer) {
string requestPath = (contentUrl.StartsWith("http") ? contentUrl : "http://" + Request.Url.DnsSafeHost + Page.ResolveUrl(contentUrl));
WebRequest request = WebRequest.Create(requestPath);
WebResponse response = request.GetResponse();
Stream responseStream = response.GetResponseStream();
var responseStreamReader = new StreamReader(responseStream);
var buffer = new char[32768];
int read;
while ((read = responseStreamReader.Read(buffer, 0, buffer.Length)) > 0) {
writer.Write(buffer, 0, read);
}
}
}
Then add the user control to your web form page:
<%# Page Language="C#" %>
<%# Register Src="~/controls/PartialViewControl.ascx" TagPrefix="mcs" TagName="PartialViewControl" %>
<h1>My MVC Partial View</h1>
<p>Below is the content from by MVC partial view (or any other URL).</p>
<mcs:PartialViewControl runat="server" contentUrl="/MyMVCView/" />
FWIW, I needed to be able to render a partial view dynamically from existing webforms code, and insert it at the top of a given control. I found that Keith's answer can cause the partial view to be rendered outside the <html /> tag.
Using the answers from Keith and Hilarius for inspiration, rather than render direct to HttpContext.Current.Response.Output, I rendered the html string and added it as a LiteralControl to the relevant control.
In static helper class:
public static string RenderPartial(string partialName, object model)
{
//get a wrapper for the legacy WebForm context
var httpCtx = new HttpContextWrapper(HttpContext.Current);
//create a mock route that points to the empty controller
var rt = new RouteData();
rt.Values.Add("controller", "WebFormController");
//create a controller context for the route and http context
var ctx = new ControllerContext(new RequestContext(httpCtx, rt), new WebFormController());
//find the partial view using the viewengine
var view = ViewEngines.Engines.FindPartialView(ctx, partialName).View;
//create a view context and assign the model
var vctx = new ViewContext(ctx, view, new ViewDataDictionary { Model = model }, new TempDataDictionary(), new StringWriter());
// This will render the partial view direct to the output, but be careful as it may end up outside of the <html /> tag
//view.Render(vctx, HttpContext.Current.Response.Output);
// Better to render like this and create a literal control to add to the parent
var html = new StringWriter();
view.Render(vctx, html);
return html.GetStringBuilder().ToString();
}
In calling class:
internal void AddPartialViewToControl(HtmlGenericControl ctrl, int? insertAt = null, object model)
{
var lit = new LiteralControl { Text = MvcHelper.RenderPartial("~/Views/Shared/_MySharedView.cshtml", model};
if (insertAt == null)
{
ctrl.Controls.Add(lit);
return;
}
ctrl.Controls.AddAt(insertAt.Value, lit);
}

Resources