how to pass class parameter through Rotativa.ActionAsPdf - model-view-controller

I want to pass class parameter in ActionAsPdf
public ActionResult Pdf(long Id)
{
var printclass = this._printService.GetPrintResults(Id);
return new ActionAsPdf("Content", new {Id = Id})
{
FileName = "abc.pdf"
}
}
public ActionResult Content(long Id)
{
//viewModel
return View("Index", viewModel);
}
It's working fine if Id alone is passed. But I want the printclass (var printclass of type class) to be passed in as the parameter as well to the Content.
I am having problem when I try to pass the class like below.
return new ActionAsPdf("Content", new {Id = Id, printclass= printclass})
{
FileName = "abc.pdf"
}
public ActionResult Content(long Id, printDTO abc)
{
var temp = abc;
//viewModel
return View("Index", viewModel);
}
The value of temp is null in the above case.

Use ViewAsPdf() instead. ActionAsPdf() accepts a RouteValueDictionary parameter.

Related

How to generate a link to an HTTP POST action with Hyprlinkr?

I'm trying to use Hyprlinkr to generate URL to the HTTP Post action. My controller looks like this:
public class MyController : ApiController {
[HttpPost]
public void DoSomething([FromBody]SomeDto someDto) {
...
}
}
with this route:
routes.MapHttpRoute(
name: "MyRoute",
routeTemplate: "dosomething",
defaults: new { controller = "My", action = "DoSomething" });
I expect to get a simple URL: http://example.com/dosomething, but it does not work. I tried two methods:
1) routeLinker.GetUri(c => c.DoSomething(null)) - throws NullReferenceException
2) routeLinker.GetUri(c => c.DoSomething(new SomeDto())) - generates invalid URL:
http://example.com/dosomething?someDto=Namespace.SomeDto
Update:
Issue opened at github:
https://github.com/ploeh/Hyprlinkr/issues/17
I found a workaround, loosely based on Mark's answer. The idea is to go over every route parameter and remove those that have [FromBody] attribute applied to them. This way dispatcher does not need to be modified for every new controller or action.
public class BodyParametersRemover : IRouteDispatcher {
private readonly IRouteDispatcher _defaultDispatcher;
public BodyParametersRemover(String routeName) {
if (routeName == null) {
throw new ArgumentNullException("routeName");
}
_defaultDispatcher = new DefaultRouteDispatcher(routeName);
}
public Rouple Dispatch(
MethodCallExpression method,
IDictionary<string, object> routeValues) {
var routeKeysToRemove = new HashSet<string>();
foreach (var paramName in routeValues.Keys) {
var parameter = method
.Method
.GetParameters()
.FirstOrDefault(p => p.Name == paramName);
if (parameter != null) {
if (IsFromBodyParameter(parameter)) {
routeKeysToRemove.Add(paramName);
}
}
}
foreach (var routeKeyToRemove in routeKeysToRemove) {
routeValues.Remove(routeKeyToRemove);
}
return _defaultDispatcher.Dispatch(method, routeValues);
}
private Boolean IsFromBodyParameter(ParameterInfo parameter) {
var attributes = parameter.CustomAttributes;
return attributes.Any(
ct => ct.AttributeType == typeof (FromBodyAttribute));
}
}
The second option is the way to go:
routeLinker.GetUri(c => c.DoSomething(new SomeDto()))
However, when using a POST method, you'll need to remove the model part of the generated URL. You can do that with a custom route dispatcher:
public ModelFilterRouteDispatcher : IRouteDispatcher
{
private readonly IRouteDispatcher defaultDispatcher;
public ModelFilterRouteDispatcher()
{
this.defaultDispatcher = new DefaultRouteDispatcher("DefaultApi");
}
public Rouple Dispatch(
MethodCallExpression method,
IDictionary<string, object> routeValues)
{
if (method.Method.ReflectedType == typeof(MyController))
{
var rv = new Dictionary<string, object>(routeValues);
rv.Remove("someDto");
return new Rouple("MyRoute", rv);
}
return this.defaultDispatcher.Dispatch(method, routeValues);
}
}
Now pass that custom dispatcher into your RouteLinker instance.
Caveat: it's very late as I'm writing this and I haven't attempted to compile the above code, but I thought I'd rather throw an attempted answer here than have you wait several more days.
Dimitry's solution got me most of the way to where I wanted, however the routeName ctor param was a problem because StructureMap doesn't know what to put in there. Internally hyprlink is using UrlHelper to generate the URI, and that wants to know the route name to use
At that point, I see why URI generation is so tricky, because it is tied to the route names in the routing config and in order to support POST, we need to associate the method, with the correct routename and that is not known at dispatcher ctor time. Default hyprlinkr assumes there is only one route config named "DefaultRoute"
I changed Dimitry's code as follows, and adopted a convention based approach, where controller methods that start with "Get" are mapped to the route named "Get" and controller methods starting with "Add" are mapped to the route named "Add".
I wonder if there are better ways of associating a method with the proper named routeConfig?
public class RemoveFromBodyParamsRouteDispatcher : IRouteDispatcher
{
private static readonly ILog _log = LogManager.GetLogger(typeof (RemoveFromBodyParamsRouteDispatcher));
public Rouple Dispatch(MethodCallExpression method,
IDictionary<string, object> routeValues)
{
var methodName = method.Method.Name;
DefaultRouteDispatcher defaultDispatcher;
if (methodName.StartsWith("Get"))
defaultDispatcher = new DefaultRouteDispatcher("Get");
else if (methodName.StartsWith("Add"))
defaultDispatcher = new DefaultRouteDispatcher("Add");
else
throw new Exception("Unable to determine correct route name for method with name " + methodName);
_log.Debug("Dispatch methodName=" + methodName);
//make a copy of routeValues as contract says we should not modify
var routeValuesWithoutFromBody = new Dictionary<string, object>(routeValues);
var routeKeysToRemove = new HashSet<string>();
foreach (var paramName in routeValuesWithoutFromBody.Keys)
{
var parameter = method.Method
.GetParameters()
.FirstOrDefault(p => p.Name == paramName);
if (parameter != null)
if (IsFromBodyParameter(parameter))
{
_log.Debug("Dispatch: Removing paramName=" + paramName);
routeKeysToRemove.Add(paramName);
}
}
foreach (var routeKeyToRemove in routeKeysToRemove)
routeValuesWithoutFromBody.Remove(routeKeyToRemove);
return defaultDispatcher.Dispatch(method, routeValuesWithoutFromBody);
}
private static bool IsFromBodyParameter(ParameterInfo parameter)
{
//Apparently the "inherit" argument is ignored: http://msdn.microsoft.com/en-us/library/cwtf69s6(v=vs.100).aspx
const bool msdnSaysThisArgumentIsIgnored = true;
var attributes = parameter.GetCustomAttributes(msdnSaysThisArgumentIsIgnored);
return attributes.Any(ct => ct is FromBodyAttribute);
}
}

ASP.NET MVC3 Physical Location of View from controller

What is the proper way to get the physical location of the View that will be served by a MVC action from inside the action?
I need the last modified time of the file for sending response headers.
The proper way to the get physical location of a view is to map its virtual path. The virtual path can be retrieved from the ViewPath property of BuildManagerCompiledView (RazorView derive from that class, and your IView instances will therefore typically have that property).
Here is an extension method that you can use:
public static class PhysicalViewPathExtension
{
public static string GetPhysicalViewPath(this ControllerBase controller, string viewName = null)
{
if (controller == null)
{
throw new ArgumentNullException("controller");
}
ControllerContext context = controller.ControllerContext;
if (string.IsNullOrEmpty(viewName))
{
viewName = context.RouteData.GetRequiredString("action");
}
var result = ViewEngines.Engines.FindView(context, viewName, null);
BuildManagerCompiledView compiledView = result.View as BuildManagerCompiledView;
if (compiledView != null)
{
string virtualPath = compiledView.ViewPath;
return context.HttpContext.Server.MapPath(virtualPath);
}
else
{
return null;
}
}
}
Use it something like this:
public ActionResult Index()
{
string physicalPath = this.GetPhysicalViewPath();
ViewData["PhysicalPath"] = physicalPath;
return View();
}
or:
public ActionResult MyAction()
{
string physicalPath = this.GetPhysicalViewPath("MyView");
ViewData["PhysicalPath"] = physicalPath;
return View("MyView");
}
That could work:
private DateTime? GetDate(string controller, string viewName)
{
var context = new ControllerContext(Request.RequestContext, this);
context.RouteData.Values["controller"] = controller;
var view = ViewEngines.Engines.FindView(context, viewName, null).View as BuildManagerCompiledView;
var path = view == null ? null : view.ViewPath;
return path == null ? (DateTime?) null : System.IO.File.GetLastWriteTime(path);
}

Cast a dynamic attribute after it's posted (Model binding)

What I have is a model which has one of it's attributes dynamic. This dynamic attribute holds one of about 50 different objects. This model is send to a view that dynamic creates the page based on which object is used. This is working perfectly ... the issue is the postback. When the model posts back the modelbinder is not able to bind the dynamic attribute. I was expecting this and thought I would be able to handle it but nothing that I tried works appart from making an action for EACH different objects.
Model
public class VM_List
{
public Config.CIType CIType { get; set; }
public dynamic SearchData { get; set; }
//Lots of static fields
}
This works
public ActionResult List_Person(VM_List Model, VM_Person_List SearchData)
{
Model.SearchData = SearchData;
//Stuff
}
public ActionResult List_Car(VM_List Model, VM_Car_List SearchData)
{
Model.SearchData = SearchData;
//Stuff
}
But what I want is a single action
public ActionResult List(VM_List Model)
{
//Stuff
}
I have tried things like
public ActionResult List(VM_List Model)
{
switch (Model.CIType)
{
case Config.CIType.Person:
UpdateModel((VM_Person_List)Model.SearchData);
break;
default:
SearchData = null;
break;
}
//Stuff
}
and a Custom modelbinder
CIType CIType = (CIType)bindingContext.ValueProvider.GetValue("CIType").ConvertTo(typeof(CIType));
switch (CIType)
{
case Config.CIType.Person:
SearchData = (VM_Person_List)bindingContext.ValueProvider.GetValue("SearchData").ConvertTo(typeof(VM_Person_List));
break;
default:
SearchData = null;
break;
}
but I can't get either to work. Any ideas?
After trying many different things I finally found a way that works.
Action:
public ActionResult List(VM_List Model)
{
//If the defaultmodelbinder fails SearchData will be an object
if(Model.SearchData.GetType() == typeof(object))
{
//Get SearchData as a Dictionary
Dictionary<string, string> DSearchData = Request.QueryString.AllKeys.Where(k => k.StartsWith("SearchData.")).ToDictionary(k => k.Substring(11), k => Request.QueryString[k]);
switch (Model.CIType)
{
case Config.CIType.Person:
Model.SearchData = new VM_Person_List(DSearchData);
break;
case Config.CIType.Car:
Model.SearchData = new VM_Car_List(DSearchData);
break;
}
//Rest of action
//..
}
and for each object make a constructor that accepts a dictionary
public VM_Car_List(Dictionary<string, string> DSearchData)
{
this.Make = Convert.ToInt32(DSearchData["Make"]);
this.Model = Convert.ToInt32(DSearchData["Model"]);
this.Year = Convert.ToInt32(DSearchData["Year"]);
// ETC
}

The name 'movieGenre' does not exist in the current context

I write a ASP.NET MVC 3 project for service a video store. I add a CRUD MovieController class and add a search feature in it. But I receive an error: "The name 'movieGenre' does not exist in the current context" for the method. Here is the code:
public ActionResult SearchIndex(string searchString)
{
var GenreList = new List<string>();
var GenreQuery = from d in db.Movies
orderby d.Genre
select d.Genre;
GenreList.AddRange(GenreQuery.Distinct());
ViewBag.movieGenre = new SelectList(GenreList);
var movies = from m in db.Movies select m;
if (!String.IsNullOrEmpty(searchString))
{
movies = movies.Where(s => s.Title.Contains(searchString));
}
if (string.IsNullOrEmpty(ViewBag.movieGenre))
{
return View(movies);
}
else
{
return View(movies.Where(x => x.Genre == movieGenre));
}
return View(movies);
}
For the last movieGenre I'm receiving this error.
If you want do use a select list you have to use ViewData instead of a ViewBag.
ViewData["Genre"] = new SelectList(GenreList);
There is no movieGenre variable.
You mean ViewBag.movieGenre.
You have an error in method declaration.
You have:
public ActionResult SearchIndex(string searchString)
Should be:
public ActionResult SearchIndex(string movieGenre, string searchString)

Cache based on Url Parameter in Asp.net MVC

i have defined a route culture/Controller/action/id... my controller contains following action..
[OutputCache(Duration=60*10)]
public ActionResult Index()
{*/do magic here/*}
is it possible to Cache contents based on Culture?
The localization complete guide presents an example of how to achieve this using the VaryByCustom parameter. In global.asax you would override the GetVaryByCustomString method:
public override string GetVaryByCustomString(HttpContext context, string value)
{
if (value == "lang")
{
return Thread.CurrentThread.CurrentUICulture.Name;
}
return base.GetVaryByCustomString(context, value);
}
and then:
[OutputCache(Duration = 60 * 10, VaryByParam = "none", VaryByCustom = "lang")]
public ActionResult Index()
{
/* do magic here */
...
}
Or if you want to rely solely on the culture route data parameter you could do this:
public override string GetVaryByCustomString(HttpContext context, string value)
{
if (value == "lang")
{
var routeData = RouteTable.Routes.GetRouteData(new HttpContextWrapper(context));
var culture = (string)routeData.Values["culture"];
if (!string.IsNullOrEmpty(culture))
{
return culture;
}
}
return base.GetVaryByCustomString(context, value);
}

Resources