Visual Studio's Intellisense knows about what View Files are available.
If I write this (typo intentional):
[HttpGet]
public ActionResult Create()
{
return View("Craete", new KeywordViewModel());
}
Then it will whinge, and not underline it and say "can't resolve View".
This is lovely.
If I want to add a layer of indirection, and pass "Create" in via another function:
[HttpGet]
public ActionResult Create()
{
return ArbitraryIndirection("Craete");
}
public ActionResult ArbitraryIndirection(string viewName)
{
return View(viewName, new KeywordViewModel());
}
Then I lose this behaviour. Intellisense doesn't know that ArbitraryIndirection is expecting a View target, so it doesn't check.
This isn't at all surprising, but it is sad.
Can I teach Intellisense to be cleverer?
Is there something that tells Intellisence that that param is special (I would guess an XML comment reference, or an attribute, maybe?) or is it magic hard-coded into Intellisence somewhere?
Can I get the write-time error-checking on the indirect View reference?
I haven't done this in about a year, but at that time, it was ReSharper which was cleverly verifying the views for me.
If you are using ReSharper, you can make use of the [AspMvcView] attribute in the JetBrains.Annotations assembly - which you can include via Nuget. A relevant guides appears on the jetbrains website at this link:
https://blog.jetbrains.com/dotnet/2011/12/07/providing-intellisense-navigation-and-more-for-custom-helpers-in-aspnet-mvc/
Example usage of the [AspMvcAction] and [AspMvcController] attributes follows (note you'll need the [AspMvcView] attribute instead, but I don't have code to hand using that)
public static MvcHtmlString WidgetWrapperAction(this HtmlHelper<dynamic> html, [AspMvcAction] string action, [AspMvcController] string controller, object parameters)
{
var routeValueDictionary = new RouteValueDictionary(parameters) { { "area", "AreaName" } };
var htmlString = html.Action(action, controller, routeValueDictionary);
if (string.IsNullOrWhiteSpace(htmlString.ToHtmlString()))
{
htmlString = html.Action("WidgetLoadFailed", "WidgetLoadFailed", new RouteValueDictionary { { "area", "AreaName" } });
}
return htmlString;
}
Hope this helps.
Related
I have an app with many widgets and their content depends on the user requesting specific route. Simply put: if widget action is requested, its content must be rendered, otherwise it's empty. Consider routes/actions like this:
~/MyApp/Index -> without model; app HTML, without any widgets
~/MyApp/Foo/{id} -> uses FooModel; if ModelState is valid, returns
Index HTML with injected partial view of Foo's widget to div#foo;
otherwise redirects to Index.
~/MyApp/Bar/{id} -> same as Foo, but different model and widget
My foo action :
public ActionResult Foo(string id) {
if (ModelState.IsValid) {
var response = FooService.GetData(id);
// Inject Foo widget to Index
}
return RedirectToAction("Index");
}
I know that it is possible to use ViewBag or other means to send variables and using the condition to decide whether to render partial view or not. But... there should be a better way to do this, right?
I use MVC's Html.RenderActionResult when I want to build shared views with non-trivial binding logic (calling the database, composing complex objects, etc). The binding logic for each widget is contained in a PartialViewResult method, which is called from the *.cshtml file using Html.RenderAction().
ContentController:
public ActionResult Index(int id)
{
var indexViewModel = new IndexViewModel
{
Id = id,
Title = "My Title",
SubHeader = "Wow its 2016"
};
return View(indexViewModel);
}
public PartialViewResult PopularContent(int id)
{
var popularContentViewModel = new List<PopularContentViewModel>();
// query by id to get popular content items
return PartialView("_PopularContent", popularContentViewModel);
}
public PartialViewResult Widget2(int id)
{
return PartialView("_Widget2Partial");
}
Index.cshtml:
#model StackOverflow.RenderAction.ViewModels.IndexViewModel
<h1>#Model.Title</h1>
<h2>#Model.SubHeader</h2>
--RenderAction will call out to the specified route.
--Note the use of the Id parameter from the viewmodel.
#{Html.RenderAction("PopularContent", "Content", new {Model.Id});}
ASP.NET MVC Attribute Routing could a be a nice solution for this:
In your controller:
public class WidgetController : Controller
{
[Route("myapp/foowidget", Name = "FooWidget")]
public ActionResult FooWidget()
{
//create any model and return any view or partial or redirect
}
[Route("myapp/boowidget/{id:int}", Name = "BooWidget")]
public ActionResult BooWidget(int id)
{
//create any model and return any view or partial or redirect
}
}
And then in a View, you can call the Route by name:
#Url.RouteUrl("FooWidget")
or
#Url.RouteUrl("BooWidget")
or
#Html.RenderPartial("FooWidget")
#Url.RouteUrl("BooWidget") will render or concatenate the id that is in current url, if url is /myapp/something/id, because of your Route attribute definition: "myapp/boowidget/{id:int}". In fact #Url.RouteUrl("BooWidget") might extract the id from any current url of the format /controllerName/action/id, though you will have to test for sure.
And notice how you can have a separation of concerns with your WidgetController and your url Routes are not dependent on that controller's name in any way. That is a nice feature of Attribute Routing, you can declare custom routes as well as organize your controllers and break from nameing convention dependency of a controllerName being part of the url controllerName/action a user sees in their browser.
In regards to Html.RenderPartial, I am not sure if RenderPartial "connects" or will be able to route to your RouteName like "FooWidget". If it does great.
If not your solution is this:
public class WidgetController : Controller
{
public ActionResult FooWidget()
{
//model, you choose, return a partial
}
public ActionResult RedirectUser()
{
//do a redirect
}
public ActionResult BooWidget()
{
//any model, any partial
}
public ActionResult BooWidget(int id)
{
//any model, any partial
}
}
Each method in your controller is single purpose, has a distinct signature and does one thing, no conditions to pass in and no decisions required.
I found some similar questions and I like the solution with the "MultipleButtonAttribute" found at here: How do you handle multiple submit buttons in ASP.NET MVC Framework?
But I come up with another solution and I thought I share it with the community.
So first of all I make a ModelBinder that handle the incoming request.
I have to made a restriction. input/button element id and name must be a prefix of "cmd".
public class CommandModelBinder<T> : IModelBinder
{
public CommandModelBinder()
{
if (!typeof(T).IsEnum)
{
throw new ArgumentException("T must be an enumerated type");
}
}
public object BindModel(System.Web.Mvc.ControllerContext controllerContext, ModelBindingContext bindingContext)
{
string commandText = controllerContext.HttpContext.Request.Form.AllKeys.Single(key => key.StartsWith("cmd"));
return Enum.Parse(typeof (T), commandText.Substring(3));
}
}
Of course it can be changed or make it to configurable via web.config of App_Start.
The next thing I make is a HtmlHelper extension to generate the necessary HTML markup:
public static MvcHtmlString CommandButton<T>(this HtmlHelper helper, string text, T command)
{
if (!command.GetType().IsEnum) throw new ArgumentException("T must be an enumerated type");
string identifier = "cmd" + command;
TagBuilder tagBuilder = new TagBuilder("input");
tagBuilder.Attributes["id"] = identifier;
tagBuilder.Attributes["name"] = identifier;
tagBuilder.Attributes["value"] = text;
tagBuilder.Attributes["type"] = "submit";
return new MvcHtmlString(tagBuilder.ToString());
}
It's still a tech demo so the html attribute and the other super overloads waiting for you to develop on your own.
Now we have to make some enumerations to try our code. They can be general or controller specific:
public enum IndexCommands
{
Save,
Cancel
}
public enum YesNo
{
Yes,
No
}
Now pair the enumerations with the binders. I do it in different file in the App_Start folder. ModelBinderConfig.
ModelBinders.Binders.Add(typeof(IndexCommands), new CommandModelBinder<IndexCommands>());
ModelBinders.Binders.Add(typeof(YesNo), new CommandModelBinder<YesNo>());
Now after we set up everything, make an action to try the codes. I kept it simple so:
public ActionResult Index()
{
return View();
}
[HttpPost]
public ActionResult Index(IndexCommands command)
{
return View();
}
And my view looks like this:
#using (Html.BeginForm())
{
#Html.CommandButton("Save", IndexCommands.Save)
#Html.CommandButton("Cancel", IndexCommands.Cancel)
}
Hope this helps to keep your code clear, type safe and readable.
I’m trying to call a action method from different controller but it´s not working. It simply skips the RedirectToAction
here's my code:
public ActionResult sendPolicy(TempPoliciesUpload TempPolicy)
{
return RedirectToAction("Insert", "Policies", new { tempPolicy = TempPolicy });
}
Please help.
You cannot send complex objects when redirecting. You will have to include each property as query string parameter. And this works only with simply scalar properties.
public ActionResult sendPolicy(TempPoliciesUpload TempPolicy)
{
return RedirectToAction("Insert", "Policies", new
{
id = TempPolicy.Id,
prop1 = TempPolicy.Prop1,
prop2 = TempPolicy.Prop2,
...
});
}
If you have complex properties you will have to include them as well so that the default model binder is able to bind the model in the target action from the query string parameters:
public ActionResult sendPolicy(TempPoliciesUpload TempPolicy)
{
return RedirectToAction("Insert", "Policies", new RouteValueDictionary
{
{ "id", TempPolicy.Id },
{ "prop1", TempPolicy.Prop1 },
{ "prop2", TempPolicy.Prop2 },
{ "prop3.subprop1", TempPolicy.Prop3.SubProp1 },
{ "prop3.subprop2", TempPolicy.Prop3.SubProp2 },
...
});
}
and your target action:
public ActionResult Insert(TempPoliciesUpload TempPolicy)
{
...
}
Another possibility is to persist this object in your backend before redirecting and then pass only the id:
public ActionResult sendPolicy(TempPoliciesUpload TempPolicy)
{
int id = Repository.Save(TempPolicy);
return RedirectToAction("Insert", "Policies", new { id = id });
}
and in your target action:
public ActionResult Insert(int id)
{
TempPoliciesUpload TempPolicy = Repository.Get(id);
...
}
I hope you have
public ActionResult Insert(TempPoliciesUpload tempPolicy)
action method in PoliciesController class.
Please see the overload of RedirectToAction here
Remove the parameter from the controller you're redirecting to and remove new { tempPolicy = TempPolicy }. See if that works (and then you localized the problem to parameter).
Most likely you need to cast it to the type of the action you redirecting to (hence Mark asked you for that signature) or play with it otherwise, maybe put in quotes (I doubt but good to try)
If even that doesn't work, check your spellings (this is why I do not like magic strings and love T4MVC) - but I doubt that either, naming looks correct.
Another likely possibility is something that solved for others here: RedirectToAction not working
Has anyone tried the first solution with complex objects?
I mean this one:
"...and your target action:..."
public ActionResult Insert(TempPoliciesUpload TempPolicy)
{
...
}
I don't think a RouteValueDictionary will convert or cast into a complex object. (Serialization must be used, I think)
My solution was passing the parameters as a RouteValueDictionary and receiving each parameters individually in the target action.
If you need to send a complex object you can try just returning the view from another controller like this:
public ActionResult sendPolicy(TempPoliciesUpload TempPolicy)
{
return View("~Views/Policies/Insert.cshtml", TempPolicy );
}
If you want this view to post back to the correct controller method you will need to specify this in the 'BeginForm' Html Helper:
...
#using(Html.BeginForm("Insert", "Policy")
{
...
}
This really isn't best practice, but it is a workaround that you can use until you fix enough of the rest of your app so that you can use the redirects properly.
For decoupling, #Darin Dimitrov answer would be suited best. However if you do not wish to pass details in the URL, so that for example the user cannot fiddle with the data, you can use the short-lived persistence TempData feature like this:
public ActionResult sendPolicy(TempPoliciesUpload TempPolicy)
{
TempData["Policy"] = TempPolicy;
return RedirectToAction("Insert", "Policies");
}
Then retrieve it in the Insert:
public ActionResult Insert()
{
TempPoliciesUpload TempPolicy = (TempPoliciesUpload)TempData["Policy"];
}
Just wondering what the actual difference between the ViewData that is bound to the MVC view and the ViewData that is bound to the #Html helper object?
I have written a page and they don't seem to refer to the same thing. Is ViewData used anywhere else in the application as another dictionary hidden under the same name?
SHORT ANSWER:
The HtmlHelper's ViewData is based on the view's data. So it has same values upon entering view code (for example, Razor or ASPX page). But you can change these ViewDatas separately.
It is used same way in AjaxHelper.
RepeaterItem has it's own ViewData, which is based on the item.
I have not found any use of different ViewData anywhere.
UPDATE:
ViewData and #Html.ViewData are different only when you use a strongly typed view. If you use a not strongly typed view, both they are equal as reference. So I think this was done to wrap the ViewData into strongly typed ViewDataDictionary<>.
SOME INVESTIGATIONS:
I have taken a look at the decompiled sources and here is what I found.
Let's see, what is #Html.ViewData:
namespace System.Web.Mvc
{
public class HtmlHelper<TModel> : HtmlHelper
{
private ViewDataDictionary<TModel> _viewData;
public ViewDataDictionary<TModel> ViewData
{
get
{
return this._viewData;
}
}
public HtmlHelper(ViewContext viewContext, IViewDataContainer viewDataContainer)
: this(viewContext, viewDataContainer, RouteTable.Routes)
{
}
public HtmlHelper(ViewContext viewContext, IViewDataContainer viewDataContainer, RouteCollection routeCollection)
: base(viewContext, viewDataContainer, routeCollection)
{
this._viewData = new ViewDataDictionary<TModel>(viewDataContainer.ViewData);
}
}
}
As we see, the ViewData is instantiated from some viewDataContainer in HtmlHelper constructor.
Let's try to see, how is this connected with the page:
namespace System.Web.Mvc {
public abstract class WebViewPage<TModel> : WebViewPage {
// some code
public override void InitHelpers() {
base.InitHelpers();
// ...
Html = new HtmlHelper<TModel>(ViewContext, this);
}
// some more code
}
}
So the current page is the viewDataContainer.
So, we see, that a new instance of a ViewData dictionary is instantiated for HtmlHelper based on the dictionary, which is stored in View. The only option, which could make the two be kinda same, if they used same Disctionary internally. Let's check that.
Here is ViewData constructor:
public ViewDataDictionary(ViewDataDictionary dictionary)
{
if (dictionary == null) {
throw new ArgumentNullException("dictionary");
}
foreach (var entry in dictionary) {
_innerDictionary.Add(entry.Key, entry.Value);
}
foreach (var entry in dictionary.ModelState) {
ModelState.Add(entry.Key, entry.Value);
}
Model = dictionary.Model;
TemplateInfo = dictionary.TemplateInfo;
// PERF: Don't unnecessarily instantiate the model metadata
_modelMetadata = dictionary._modelMetadata;
}
As we can see, entries a just copied, but a different underlying _innerDictionary is used.
I edited my whole question, so do not wonder :)
Well, I want to have an ActionResult that takes domain model data and some additional parameters, i.e page index and page size for paging a list. It decide itself if it returns a PartialViewResult or a ViewResult depending on the kind of web request (ajax request or not).
The reffered data shall be mapped automatically by using an IMappingService, which is responsible for transforming any domain model data into a view model.
The MappingService uses AutoMapper for simplicity.
MappingActionResult:
public abstract class MappingActionResult : ActionResult
{
public static IMappingService MappingService;
}
BaseHybridViewResult:
public abstract class BaseHybridViewResult : MappingActionResult
{
public const string defaultViewName = "Grid";
public string ViewNameForAjaxRequest { get; set; }
public object ViewModel { get; set; }
public override void ExecuteResult(ControllerContext context)
{
if (context == null) throw new ArgumentNullException("context");
var usePartial = ShouldUsePartial(context);
ActionResult res = GetInnerViewResult(usePartial);
res.ExecuteResult(context);
}
private ActionResult GetInnerViewResult(bool usePartial)
{
ViewDataDictionary viewDataDictionary = new ViewDataDictionary(ViewModel);
if (String.IsNullOrEmpty(ViewNameForAjaxRequest))
{
ViewNameForAjaxRequest = defaultViewName;
}
if (usePartial)
{
return new PartialViewResult { ViewData = viewDataDictionary, ViewName = ViewNameForAjaxRequest };
}
return new ViewResult { ViewData = viewDataDictionary };
}
private static bool ShouldUsePartial(ControllerContext context)
{
return context.HttpContext.Request.IsAjaxRequest();
}
}
AutoMappedHybridViewResult:
public class AutoMappedHybridViewResult<TSourceElement, TDestinationElement> : BaseHybridViewResult
{
public AutoMappedHybridViewResult(PagedList<TSourceElement> pagedList)
{
ViewModel = MappingService.MapToViewModelPagedList<TSourceElement, TDestinationElement>(pagedList);
}
public AutoMappedHybridViewResult(PagedList<TSourceElement> pagedList, string viewNameForAjaxRequest)
{
ViewNameForAjaxRequest = viewNameForAjaxRequest;
ViewModel = MappingService.MapToViewModelPagedList<TSourceElement, TDestinationElement>(pagedList);
}
public AutoMappedHybridViewResult(TSourceElement model)
{
ViewModel = MappingService.Map<TSourceElement, TDestinationElement>(model);
}
public AutoMappedHybridViewResult(TSourceElement model, string viewNameForAjaxRequest)
{
ViewNameForAjaxRequest = viewNameForAjaxRequest;
ViewModel = MappingService.Map<TSourceElement, TDestinationElement>(model);
}
}
Usage in controller:
public ActionResult Index(int page = 1)
{
return new AutoMappedHybridViewResult<TeamEmployee, TeamEmployeeForm>(_teamEmployeeRepository.GetPagedEmployees(page, PageSize));
}
So as you can see the IMappingService is hidden. The controller should not know anything about the IMappingService interface, when AutoMappedHybridViewResult is used.
Is the MappingActionResult with the static IMappingServer appropriate or am I violating the DI principle?
I think a better design is to have a ViewResultFactory that depends on IMappingService, then you can inject that into your controller. Then you call it like so:
public class MyController : Controller
{
IViewResultFactory _viewResultFactory;
ITeamEmployeeRepository _teamEmployeeRepository;
public MyController(IViewResultFactory viewResultFactory)
{
_viewResultFactory = viewResultFactory;
}
public ActionResult MyAction(int page, int pageSize)
{
return
_viewResultFactory.GetResult<TeamEmployee, TeamEmployeeForm>(
_teamEmployeeRepository.GetPagedEmployees(page, pageSize));
}
}
The implementation would like this (you would need to create overloads for each of your HybridViewResult constructors):
public HybridViewResult<TSourceElement, TDestinationElement> GetResult<TSourceElement, TDestinationElement>(PagedList<TSourceElement> pagedList)
{
return new HybridViewResult<TSourceElement, TDestinationElement>(_mappingService, pagedList);
}
That way you hide the implementation from your controllers, and you don't have to depend on the container.
There are a few different points that you could inject IMappingService. http://codeclimber.net.nz/archive/2009/04/08/13-asp.net-mvc-extensibility-points-you-have-to-know.aspx is a good site for help in picking the appropriate extensibility points for .NET MVC.
If you want to stick with having this functionality be a derived ActionResult, then I think you could put the dependency in the ActionInvoker if you want to, but the Controller makes more sense to me. If you don't want the IMappingService in the Controller, you could always wrap it in a HybridViewResultFactory, and access that object in the Controller. In that case your shortcut methods would look like:
public HybridViewResult<TSourceElement, TDestinationElement> AutoMappedHybridView<TSourceElement,TDestinationElement>(PagedList<TSourceElement> pagedList, string viewNameForAjaxRequest)
{
HybridViewResultFactory.Create<TSourceElement, TDestinationElement>(pagedList, viewNameForAjaxRequest);
}
etc.
I'm not sure why you need to use an ActionResult, but if there is no reason that makes it explicitly necessary, you could create a HybridViewModel class and a HybridViewModelBinder class that is injected with the mapping service dependency.
I am assuming you want to use constructor injection, but if you have the StructureMap dependency in your UI assembly, you could access a static dependency resolver class (like Clowers said).
This question would be easier to give a definite answer to if I understood why you using an ActionResult.
It seems like you are using the action result to handle two functionalities that do not necessarily go together all the time, and that could be used separately. Also, there is not a clear indication that it needs to be in an ActionResult.
Presumably, you could (a) leverage the Automapper functionality for results other than html (ViewResult) output, and (b) you could leverage the functionality of auto-detecting ajax requests without needing to automap the model.
It seems to me like the automapping of the view model could be used to inject the view model into the controller action directly, thus removing the controller's dependency on the IMappingService. What you would need is a ModelBinder class to be injected with your IMappingService (the implementation of which I assume contains a repository or datastore type dependency).
Here is a good article explaining how to leverage model binders: http://odetocode.com/blogs/scott/archive/2009/04/27/6-tips-for-asp-net-mvc-model-binding.aspx.
Then you can overwrite the DefaultModelBinder in the classes that need to be Automapped as follows:
public ActionResult DoItLikeThis([AutoMap(typeof(MyDomainModelClass))]MyViewModelClass viewModel){
//controller action logic
}
Now, regarding the HybridViewResult, I would suggest that you handle this with an Action Filter instead. So, you could just use ActionResult or ViewResultBase as the Result type of your action method and decorate it with an action filter, i.e.:
[AutoSelectViewResult]
public ViewResultBase AndDoThisLikeSo(){
//controller action logic
}
I think overall this will be a much better solution than coupling these two functionalities to an ActionResult.