Unable to use extension method in razor view - c#

I am trying to display images from the local storage(not Content folder). The image tag I am using looks like this.
#if (Model.PreviousCoverPic != null)
{
<img src="#Url.Action("ServeImage", "Profile",new {path = Model.PreviousCoverPic})" alt="Previous Profile Pic" />
}
I made the ServerImage method an extension method since it will be used by more than one controller. Here is the code for the method:
public static ActionResult ServeImage(this System.Web.Mvc.Controller controller, string path)
{
Stream stream = new FileStream(path, FileMode.Open);
FileResult fileResult = new FileStreamResult(stream, "image/jpg");
return fileResult;
}
It returns a FileResult of the image so that it can be displayed. However, when the view is rendered it doesn't show the image. I checked the Model.PreviousCoverPic value and it is not null. What am I missing here? How can I achieve displaying methods from a local folder? Also, I followed the answer in this question and added the name space of the class which contains the extension method but the image still doesn't get rendered in the view.

According to the following sources
Can MVC action method be static or extension method
And this answer
extend ASP.NET MVC action method,How to do return View
The extension method wont work with routing the Url.Action. You can use inheritance to make a base class with the action. that way all inherited classes will have the action and calls to Url.Action will be valid.
public abstract class MyBaseController : Controller {
public ActionResult ServeImage(string path) {...}
}
public class ConcreteController : MyBaseController {...}

Related

Call method helper with link data parameter

I have a series of dynamic links on my view that render like this:
Course Image <strong>(600x384)</strong>
I have a method that looks like this:
public class CantoDownloadPreset
{
public static FileResult DownloadPreset(string preset)
{
...
}
}
how can I call the helper DownloadPreset method and send data-itemid parameter.
CantoDownloadPreset should be a controller in a named file CantoDownloadPresetController.cs inside Controllers Folder.
Then if you need to do a GET, inherit Controller and remove static:
public class CantoDownloadPresetController : Controller
public FileResult DownloadPreset(string preset)
{
...
}
But take consideration if you want to return a FileResult and where. This question implies a lot of research about how MVC Web Controller works and you should be more in focus. But anyway, the link should be then (only for GET of course):
/method/action/value
then:
/CantoDownloadPreset/DownloadPreset/?preset=value

View error when invoking methods from base controller in MVC 5

I have the following controllers
public class StoreController : Controller
{
public ActionResult Index()
{
var model = new SomeViewModel();
return View(model);
}
}
and
public class SofiaStoreController : StoreController
{
public ActionResult GetIndex(string city)
{
return base.Index();
}
}
When invoking the base Index method from the derived class I get this error:
The view 'getindex' or its master was not found or no view engine
supports the searched locations. The following locations were
searched:
Seems the GetIndex() method looks for a view in the derived controller's view folder by default even though no View() method is called but since there isn't such the error occurs.
Any idea why the method implicitly looks for a view and how to overcome the error ?
EDIT: After spending some time research the problem I came across these two posts: http://howtoprogram.eu/question/asp-net-c-asp-net-mvc-inherited-controller-using-base-view,2445 and http://www.davidwhitney.co.uk/Blog/2010/01/19/asp-net-mvc-view-engine-that-supports-view-path-inheritance/ Seems that controller inheritance is not that popular or straightforward decision. The solutions to my problem could be :
1. Not to use controller inheritance
2. Creating a custom view engine as shown in the second link (advanced)
3. As other people mentioned bellow - using full path to the view or RedirectToAction also could work
It does look for a view based on the Action method name you originally called. You can always override this behavior if you use the overloaded View() method that accepts the view name/path:
public class StoreController : Controller
{
public ActionResult Index(string viewName = "Index")
{
var model = new SomeViewModel();
return View(viewName, model);
}
}
public class SofiaStoreController : StoreController
{
public ActionResult GetIndex(string city)
{
return base.Index();
}
}

How to withdraw code from an action method into helper in Asp.Net MVC 3?

I'm writing my app using Asp.Net MVC 3. In my controller I have two action methods with the very same code apart from one line. Here it is:
[HttpPost]
public ActionResult EditPost(Post post)
{
if (ModelState.IsValid)
{
_postsRepository.UpdatePost(post);
return RedirectToAction("NewsFeed");
}
return View("EditPost", post);
}
[HttpPost]
public ActionResult AddPost(Post post)
{
if (ModelState.IsValid)
{
_postsRepository.UpdatePost(post);
return RedirectToAction("NewsFeed");
}
return View("AddPost", post); // the return view is different
}
So, I want to withdraw all this code into helper method.
What I've already tried:
1) I tried to put all the code into helper method and pass as parameters ModelState.IsValid and View name. And then in AddPost and EditPost I call this helper method instead of code listed above. Here is the new code:
[HttpPost] // also tried without this attribute
public ActionResult HelperPost(Post post, string viewName, bool modelState)
{
if (modelState)
{
_postsRepository.UpdatePost(post);
return RedirectToAction("NewsFeed");
}
return View(viewName, post);
}
[HttpPost] // also tried without this attribute
public void AddPost(Post post)
{
HelperPost(post, "AddPost", ModelState.IsValid);
}
The EditPost code is almost the same. The view name is "EditPost".
When I run the app and AddPost method executes the validation works and the new post is created but this line never executes:
return RedirectToAction("NewsFeed");
So I'm redirected to "AddPost" view again and again.
2) Also tried to redirect to HelperPost method instead of calling it withing AddPost and EditPost. The result is still the same: seems like RedirectToAction("NewsFeed") doesn't execute. (Here I neglected the validation just to simplify the example, cause I would have to create new model with properties: Post post, string viewName, bool modelState). The code:
[HttpPost] // tried without attribute
public void AddPost(Post post)
{
return RedirectToAction("HelperPost", post);
}
[HttpPost] // tried without attribute
public RedirectToRouteResult HelperUpdatePost(Post post)
{
_postsRepository.UpdatePost(post);
return RedirectToAction("NewsFeed");
}
So, How could I refactor my code so my action methods (EditPost and AddPost) would not contain the same chunk of code?
p.s. I need different views for AddPost and EditPost methods cause the "back to content" links in them are different. So, I can't just redirect to the EditPost view from AddPost method.
Thanks for help in advance!
Just put your "back to content" link in the model, then use the same view for both, then you can use the same HttpPost method. Saves having to duplicate everything.
I would solve it like this:
I would withdraw the method implementation into separate private
method. This method will be invoked by each of the public action
methods. Since the View name differs for both methods I would pass
the view name as parameter to the private method.
The private method doesn't need the HttpPostAttribute!
Don't forget to declare Add and Edit action methods as returning
ActionResult! As parameter they will expect only Post, the view name has to be hard-coded into the action methodsiteslf ;-)
I hope this helps.

View to String from another controller

i have done as Vdex suggested here:
https://stackoverflow.com/a/5801502/973485
And used the RenderPartialToString method he found. And it works perfectly like this:
public class HomeController : Controller
{
public ActionResult Index()
{
return View();
}
public ActionResult Test()
{
string t = ViewToString.RenderPartialToString("Index", null, ControllerContext);
return Content(t);
}
}
But if i want to render the Home > Index from another Controller, i get:
Value cannot be null.
Parameter name: controllerContext
Like this:
public class FooController : Controller
{
public ActionResult Index()
{
string t = ViewToString.RenderPartialToString("Index", null, new HomeController().ControllerContext);
return Content(t);
}
}
Is there any way to pass a View from another Controller to a string? I have tried many different methods, and it all of them fails at the ControllerContext. Many thanks!
Update: Why i need to do this:
Imagine i have a website full of widgets, the amount of widgets on each page is dynamic, so i cannot hardcode them in my cshtml file. But in that file there are different areas defined where the widgets gets printet out. To print out these widget i have a list of IWidgetController wich contains alle the different Widgets available, and the interface sais that they need to containe a ActionResult for edit, new and view. example of widgets: CalenderController, NewsController, GalleryController and so on... So in those areas i need to print out the content of each of those Controllers. Now i could also load the URLHTML but i figured doing it from the inside would be faster... right?
Try this:
string t = ViewToString.RenderPartialToString("Index", null, this.ControllerContext);
Anyway, why do you need to convert to a string?

Render Controller Action To String (Using Virtual Path, No action/controller name) ASP.NET MVC 3

Ok, im developing a MVC CMS like website and when declaring routes i used following pattern. I encapsulate action name and controller name into a class like so
public class UrlUtilsUnhandledErrorsExtensions
{
private readonly UrlHelper _urlHelper;
public UrlUtilsUnhandledErrorsExtensions(UrlHelper urlHelper)
{
_urlHelper = urlHelper;
}
public String GetLatestErrors()
{
return _urlHelper.Action("GetLatestErrors", "UnhandledErrors");
}
}
Then instead of writing
#Url.Action("GetLatestErrors", "UnhandledErrors")
I write
#Url.Action(Url.Utils().UnhandledErrors().GetLatestErrors())
I find this approach much more easier to maintain, because if controller name changes i only have to change one class.
This works fine with any links, controller redirects (return Redirect(...)) and just anything that accept virtual path which is returned by
public String GetLatestErrors()
{
return _urlHelper.Action("GetLatestErrors", "UnhandledErrors");
}
But here comes the problem: i cant use Html.Action() with this approach. It requires controller name and action name, but instead i want it to use virtual path.
After digging around and studying MVC source code i realized that i will need to write my own Html.Action extension method that will just accept virtual path.
So here is my solution
public void ActionFromUrl(this HtmlHelper htmlHelper, String url)
{
RouteValueDictionary rvd = null;
rvd = new RouteValueDictionary();
String action = String.Empty;
String controller = String.Empty;
foreach (Route route in htmlHelper.RouteCollection)
{
if (route.Url == url.Substring(1)) // url starts with / for some reason
{
action = route.Defaults["action"] as String;
controller = route.Defaults["controller"] as String;
break;
}
}
RequestContext rc = ((MvcHandler)HttpContext.Current.CurrentHandler).RequestContext;
rc.RouteData.Values["action"] = action;
rc.RouteData.Values["controller"] = controller;
IControllerFactory factory = ControllerBuilder.Current.GetControllerFactory();
IController controllerImpl = factory.CreateController(rc, controller);
controllerImpl.Execute(rc);
}
It works, but since its based on Html.RenderAction method it just writes directly to output, so in my view when i write following code
#{ Html.ActionFromUrl(Url.Utils().UnhandledErrors().GetLatestErrors()); }
It renders my partial first, all above everything and then rest of html follows.
This is not the result i want, so i have to find out the way of rendering the result to string as Html.Action do. I already looked into the source code with dotPeek but coudn't figure out how to mix it altogether.
My question is: Am i doing something wrong ? Or how can i write Html.Action overload so it accepts virtual path and returns MvcHtmlString ?
in CMS, you probably do not need the whole convention-based view rendering, you will (sooner or later) want render custom templates "to string" and merge result layout by your (most probably dynamic/configurable) rules. Take a look on RazorEngine project.
http://razorengine.codeplex.com/

Categories