Does a view exist in ASP.NET MVC? - asp.net-mvc

Is it possible to determine if a specific view name exists from within a controller before rendering the view?
I have a requirement to dynamically determine the name of the view to render. If a view exists with that name then I need to render that view. If there is no view by the custom name then I need to render a default view.
I'd like to do something similar to the following code within my controller:
public ActionResult Index()
{
var name = SomeMethodToGetViewName();
// The 'ViewExists' method is what I've been unable to find.
if (ViewExists(name))
{
retun View(name);
}
else
{
return View();
}
}

private bool ViewExists(string name)
{
ViewEngineResult result = ViewEngines.Engines.FindView(ControllerContext, name, null);
return (result.View != null);
}
For those looking for a copy/paste extension method:
public static class ControllerExtensions
{
public static bool ViewExists(this Controller controller, string name)
{
ViewEngineResult result = ViewEngines.Engines.FindView(controller.ControllerContext, name, null);
return (result.View != null);
}
}

What about trying something like the following assuming you are using only one view engine:
bool viewExists = ViewEngines.Engines[0].FindView(ControllerContext, "ViewName", "MasterName", false) != null;

Here's another [not necessarily recommended] way of doing it
try
{
#Html.Partial("Category/SearchPanel/" + Model.CategoryKey)
}
catch (InvalidOperationException) { }

In asp.net core 2.x and aspnet6 the ViewEngines property no longer exists so we have to use the ICompositeViewEngine service. This a variant of the accepted answer using dependency injection:
public class DemoController : Controller
{
private readonly IViewEngine _viewEngine;
public DemoController(ICompositeViewEngine viewEngine)
{
_viewEngine = viewEngine;
}
private bool ViewExists(string name)
{
ViewEngineResult viewEngineResult = _viewEngine.FindView(ControllerContext, name, true);
return viewEngineResult?.View != null;
}
public ActionResult Index() ...
}
For the curious: The base interface IViewEngine is not registered as a service so we must inject ICompositeViewEngine instead. The FindView() method however is provided by IViewEngine so the member variable may use the base interface.

If you want to re-use this across multiple controller actions, building on the solution given by Dave, you can define a custom view result as follows:
public class CustomViewResult : ViewResult
{
protected override ViewEngineResult FindView(ControllerContext context)
{
string name = SomeMethodToGetViewName();
ViewEngineResult result = ViewEngines.Engines.FindView(context, name, null);
if (result.View != null)
{
return result;
}
return base.FindView(context);
}
...
}
Then in your action simply return an instance of your custom view:
public ActionResult Index()
{
return new CustomViewResult();
}

ViewEngines.Engines.FindView(ViewContext.Controller.ControllerContext, "View Name").View != null
My 2 cents.

Here's how to do it in Razor for Core 2.2 etc. Note that the call is "GetView", not "Find View)
#using Microsoft.AspNetCore.Mvc.ViewEngines
#inject ICompositeViewEngine Engine
...
#if (Engine.GetView(scriptName, scriptName, isMainPage: false).Success)
{
#await Html.PartialAsync(scriptName)
}

Related

MVC Controller inside ApiController with ControllerContext

Short story about what I am doing and why. I had been doing view to string conversion in Mvc project, but suddenly all project moved to the REST API. But I had to use razor engine to convert my view with all model data there, so I was trying to use directly from api, but it didn't work for me, so I decided to create a Mvc controller and call it directly from API, what is missing, only ControllerContext, because when I create controller directly, it appears to be null.
Here is my Api controller
public class AttachmentController : ApiController
{
[HttpGet]
public async Task<IHttpActionResult> Get(long id)
{
try
{
var mvcController = new AttachmentMvcController();
var result = await mvcController.Get();
return Ok(result);
}
catch (Exception e)
{
return InternalServerError(e);
}
}
}
and this is my Mvc Controller
public class AttachmentMvcController : Controller
{
public AttachmentMvcController(){ }
public async Task<byte[]> Get()
{
string result;
// !!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!
// HERE IS MY PROBLEM, I NEED this.ControllerContext, but it is null !!!
if (ControllerContext == null)
{
// create it and do task
var factory = DependencyResolver.Current.GetService<IControllerFactory>() ?? new DefaultControllerFactory();
AttachmentMvcController controller = factory.CreateController(ctx.RequestContext, "AttachmentMvc") as AttachmentMvcController;
RouteData route = new RouteData();
route.Values.Add("action", "ActionThatUsesControllerContext"); // ActionName, but it not required
ControllerContext newContext = new ControllerContext(new HttpContextWrapper(System.Web.HttpContext.Current), route, controller);
controller.ControllerContext = newContext;
result = await controller.ActionThatUsesControllerContext(id);
}
else
{
result = await this.ActionThatUsesControllerContext(id);
}
return result;
}
private async Task<byte[]> ActionThatUsesControllerContext()
{
{....}
// here I am trying to use helper that uses that controller context
string viewAsString = ControllerContext.RenderPartialToString("MyView", requestModel);
{....}
}
}
If anyone has idea how to get that ControllerContext or any other ways to get my razor engine render view inside ApiController, please share.

MVC4 accessing method of one controller to the other

I have some inbuilt method in BootstrapBaseController.cs like
public void Success(string message)
{
TempData.Add(Alerts.SUCCESS, message);
}
I haven't used it yet but I guess it shows some success message that i pass to this method.
I want to use this method in my other controller called PortalController. In following method I want to show success message when a user is added to database.
Suggestions?
[HttpPost]
public ActionResult Register(RegisterModel user)
{
if (ModelState.IsValid && (user.Password == user.ConfirmPassword))
{
var regUser = _db.Users.Create();
regUser.UserName = user.UserName;
regUser.Password = user.Password;
_db.Users.Add(regUser);
_db.SaveChanges();
}
return View();
}
You'll need to make PortalController inherits from BootstrapBaseController.
public class PortalController : BootstrapBaseController
If this is not an option you may need to move that common method to a third class used by both controllers.
Make PortalController inherit from BootstrapBaseController
Before returning the View call Sucess and set the message
On your _layout.cshtml view (and I'm assuming all your views inherit use this layout) check for that temp variable and display your message
Updated Controller method:
[HttpPost]
public ActionResult Register(RegisterModel user)
{
if (ModelState.IsValid && (user.Password == user.ConfirmPassword))
{
var regUser = _db.Users.Create();
regUser.UserName = user.UserName;
regUser.Password = user.Password;
_db.Users.Add(regUser);
_db.SaveChanges();
}
Success("User Created successfully");
return View();
}
_Layout.cshtml
#if (TempData["VariableName"] != null)
{
<div>#TempData["VariableName"]</div>
}

Asp.net mvc what is the best practice of rebuilding ViewModel?

On POST , if validation failed and before sending back the ViewModel to the same View with Model State errors, do you rebuild ViewModel for all SelectLists, ReadOnly fields etc?
right now I have separate methods for Fill First Time(for GET Edit-Method) / Rebuild ViewModels from domain objects, what is the best practice so I can be DRY and also not have to change two methods any time I add a new readonly property to ViewModel?
My Solution: Followed this Pattern
Followed pattern suggested here: https://stackoverflow.com/a/2775656/57132
In IModelBuilder Implementation
Build(..)
{
var viewModel = new ViewModel();
// and Fill all Non-ReadOnly fields
...
...
call CompleteViewModel(viewModel)
}
CompleteViewModel(ViewModel viewModel)
{
//Fill all ReadOnly & SelectLists
...
}
The reason I went with this solution is because I don't want to store stuff on server to retrieve across the HTTP Requests
I don't rebuild it, because I don't stay at POST. I follow POST-REDIRECT-GET pattern, so if I post to /User/Edit/1 using POST HTTP method, I get redirected to /User/Edit/1 uasing GET.
ModelState is transferred to TempData to follow Post-Redirect-Get and be availabe at GET call. View model is built in one place, at GET call. Example:
[HttpPost]
[ExportModelStateToTempData]
public ActionResult Edit(int id, SomeVM postedModel)
{
if (ModelState.IsValid) {
//do something with postedModel and then go back to list
return RedirectToAction(ControllerActions.List);
}
//return back to edit, because there was an error
return RedirectToAction(ControllerActions.Edit, new { id });
}
[ImportModelStateFromTempData]
public ActionResult Edit(int id)
{
var model = //create model here
return View(ControllerActions.Edit, model);
}
This is code for attributes importing/exporting ModelState:
public abstract class ModelStateTempDataTransferAttribute : ActionFilterAttribute
{
protected static readonly string Key = typeof(ModelStateTempDataTransferAttribute).FullName;
}
public class ExportModelStateToTempDataAttribute : ModelStateTempDataTransferAttribute
{
public override void OnActionExecuted(ActionExecutedContext filterContext)
{
//Only export when ModelState is not valid
if (!filterContext.Controller.ViewData.ModelState.IsValid)
{
//Export if we are redirecting
if ((filterContext.Result is RedirectResult) || (filterContext.Result is RedirectToRouteResult))
{
filterContext.Controller.TempData[Key] = filterContext.Controller.ViewData.ModelState;
}
}
base.OnActionExecuted(filterContext);
}
}
public class ImportModelStateFromTempDataAttribute : ModelStateTempDataTransferAttribute
{
public override void OnActionExecuted(ActionExecutedContext filterContext)
{
ModelStateDictionary modelState = filterContext.Controller.TempData[Key] as ModelStateDictionary;
if (modelState != null)
{
//Only Import if we are viewing
if (filterContext.Result is ViewResult)
{
filterContext.Controller.ViewData.ModelState.Merge(modelState);
}
else
{
//Otherwise remove it.
filterContext.Controller.TempData.Remove(Key);
}
}
base.OnActionExecuted(filterContext);
}
}
The simplest solution would be to pass in you viewModel to the method and account for null
private MyViewModel BuildViewModel(MyViewModel model = null)
{
model = model ?? new MyViewModel();
model.ReadOnlyList = new .....
.
.
return model;
}
for Create:
var model = BuildViewModel();
for rebuild:
model = buildViewModel(model);
I like #LukLed's answer above - it looks very interesting. If you want another option, here's what I currently do.
In my service layer, I have a method to build my view model. I call that on GET and return the the view model to the view. On POST, I build the model from the incoming ID and then TryUpdateModel(model). From there, you can do whatever you like (save, check model state, etc.). With this method, you only have 1 build method and only have to update it once if your model changes (i.e. add/remove properties in the future, etc.).
[HttpGet]
public ActionResult AssessFocuses(int apaID)
{
var model = this.apaService.BuildAssessFocusesViewModel(apaID);
return this.View(model);
}
[HttpPost]
public ActionResult AssessFocuses(int apaID, string button)
{
var model = this.apaService.BuildAssessFocusesViewModel(apaID);
this.TryUpdateModel(model);
switch (button)
{
case ButtonSubmitValues.Back:
case ButtonSubmitValues.Next:
case ButtonSubmitValues.Save:
case ButtonSubmitValues.SaveAndClose:
{
try
{
this.apaService.SaveFocusResults(model);
}
catch (ModelStateException<AssessFocusesViewModel> mse)
{
mse.ApplyTo(this.ModelState);
}
if (!this.ModelState.IsValid)
{
this.ShowErrorMessage(Resources.ErrorMsg_WEB_ValidationSummaryTitle);
return this.View(model);
}
break;
}
default:
throw new InvalidOperationException(string.Format(Resources.ErrorMsg_WEB_InvalidButton, button));
}
switch (button)
{
case ButtonSubmitValues.Back:
return this.RedirectToActionFor<APAController>(c => c.EnterRecommendationsPartner(model.ApaID));
case ButtonSubmitValues.Next:
return this.RedirectToActionFor<APAController>(c => c.AssessCompetenciesPartner(model.ApaID));
case ButtonSubmitValues.Save:
this.ShowSuccessMessage(Resources.Msg_WEB_NotifyBarSuccessGeneral);
return this.RedirectToActionFor<APAController>(c => c.AssessFocuses(model.ApaID));
case ButtonSubmitValues.SaveAndClose:
default:
return this.RedirectToActionFor<UtilityController>(c => c.CloseWindow());
}
}

How to get controller name in another class?

I am developing MVC application.
I want to pass controller to some other class for validation purpose.
After passing the controller, I am unable to get the controller name in that class.
[HttpPost]
public ActionResult Create(Location location)
{
if (ModelState.IsValid)
{
Validations v = new Validations();
boolean b;
//passing controller in another class's method
b = v.ValidProperty(location);
if (ValidProperties == true)
{
db.Locations.Add(location);
db.SaveChanges();
return RedirectToAction("Index");
}
}
}
Getting controller in below method
public void ValidProperty(object Controller)
{
//Gives an error in below line
string CtrName = (string)Controller.ToString;
}
How to get the controller Name ?
b = v.ValidProperty(ControllerContext);
you may be wondering where am I initializing ControllerContext variable.
well you don't have to
public void ValidProperty(ControllerContext ControllerContext)
{
// do your logic here.
}
You should call ControllerContext.RouteContext.GetRequiredString("controller")
To get the name of the controller, you can just use
RouteData.Values["controller"]

Overflowed (i.e. querystring) RouteData back out of the controller into the view

Anyone understand why the following doesn't work?
What I want to do is copy current route data plus whatever I add via an anonymous object into new routedata when forming new links on the view.
For example if I have the parameter "page" as a non route path (i.e. so it overflows the route path and its injected into the method parameter if a querystring is present) e.g.
public ActionResult ChangePage(int? page) { }
and I want the View to know the updated page when building links using helpers. I thought the best way to do this is with the following:
public ActionResult ChangePage(int? page)
{
if(page.HasValue)
RouteData.Values.Add("Page", page);
ViewData.Model = GetData(page.HasValue ? page.Value : 1);
}
Then in the view markup I can render my next, preview, sort, showmore (any links relevant) with this overload:
public static class Helpers
{
public static string ActionLinkFromRouteData(this HtmlHelper helper, string linkText, string actionName, object values)
{
RouteValueDictionary routeValueDictionary = new RouteValueDictionary();
foreach(var routeValue in helper.ViewContext.RouteData.Values)
{
if(routeValue.Key != "controller" && routeValue.Key != "action")
{
routeValueDictionary[routeValue.Key] = routeValue;
}
}
foreach(var prop in GetProperties(values))
{
routeValueDictionary[prop.Name] = prop.Value;
}
return helper.ActionLink(linkText, actionName, routeValueDictionary;
}
private static IEnumerable<PropertyValue> GetProperties(object o)
{
if (o != null) {
PropertyDescriptorCollection props = TypeDescriptor.GetProperties(o);
foreach (PropertyDescriptor prop in props) {
object val = prop.GetValue(o);
if (val != null) {
yield return new PropertyValue { Name = prop.Name, Value = val };
}
}
}
}
private sealed class PropertyValue
{
public string Name { get; set; }
public object Value { get; set; }
}
}
I have posted the code only to illustrate the point. This doesn't work and doesn't feel right... Pointers?
Pass the page info into ViewData?
PagedResultsInfo (or something) sounds like a class you could write too... we do.

Resources