I am wondering if its possible to take an existing partial view's logic and reuse it for dynamic email generation, within a preprocessor template?
When looking through the T4ToolKit's intellisense options for
<## import namespace="System.Web.Mvc" #>
Mvc's namespace does not appear, is it possible to include the namespace and call
Html.RenderPartial("viewName", this.Model)
from within a preprocessor template?
i.e.
<## template language="C#" #>
This is a header
<#= Html.RenderPartial("<%PATH%>/MyPartialRazerView", this.Model) #>
This is a Footer
<#+
public MyType Model { get; set; }
#>
so I may programmatically access my template, reuse a view's display logic and build, say an email on the fly (I know the Email line is nonsense, just short hand for simplicity)
var template = MyTemplate(){ Model = MyViewModel };
Email.Send(emailAddress, title, template.TransformText(), null) etc..
TIA
This can be done for sure, but you need to use a different extension methods than RenderPartial, because these write directly to the response. I tried Partial extension methods, which return the MvcHtmlString which works fine in the template. This is my test T4 runtime template, RuntimeTextTemplate1.tt:
<## template language="C#" #>
<## import namespace="System.Web.Mvc.Html" #>
LogOnPartial:
<#= Html.Partial("_LogOnPartial") #>
Then you also need to jump some ASP.NET MVC hoops to get the actual HtmlHelper instance into your template.
I created a partial class, to add the Html property to the template and instantiate the HtmlHelper and to provide a constructor:
public partial class RuntimeTextTemplate1
{
public HtmlHelper Html
{
get;
private set;
}
public RuntimeTextTemplate1(ViewContext viewContext, IViewDataContainer viewDataContainer)
{
Html = new HtmlHelper(viewContext, viewDataContainer);
}
}
A HtmlHelper needs a ViewContext and IViewDataContainer to be created and those in turn have another dependencies. I provided what is needed from the controller using some dummy classes:
public class HomeController : Controller
{
public ActionResult TemplateTest()
{
var viewContext = new ViewContext(ControllerContext, new DummyView(), ViewData, TempData, TextWriter.Null);
var template = new RuntimeTextTemplate1(viewContext, new ControllerViewDataContainer(this));
return Content(template.TransformText());
}
}
public class DummyView : IView
{
public void Render(ViewContext viewContext, TextWriter writer)
{
// Do nothing
}
}
public class ControllerViewDataContainer : IViewDataContainer
{
private Controller controller;
public ViewDataDictionary ViewData
{
get { return controller.ViewData; }
set { }
}
public ControllerViewDataContainer(Controller controller)
{
this.controller = controller;
}
}
And I successfully get the template output out of it.
So while this can be done, it depends on your specific situation, how exactly you need to use it, how your view is parametrized, and how you can assemble the required classes together to get to the HtmlHelper instance.
In the end, you may find that making the template the primary source of the required output, and using this in your views and outside them is easier than the other way around.
Related
What is the best process to extend the Razor view-engine to add additional keywords?
I'm not a fan of the dynamic ViewBag property, so for all of my pages I define both a strongly-typed ViewModel POCO, but also a strongly-typed ViewData object:
public abstract class BaseViewData<TModel,TController> : ViewDataDictionary<TModel>
(TController is specified to optionally allow strongly-typed callbacks to the parent Controller)
This is so I can have compile-time verified members, like String PageTitle (in a site-wide base class) and per-page members - it works in tandem with the ViewModel: the BaseViewData-subclass contains one-way data, and the ViewModel-class contains two-way data. This works great when you tweak the MSBuild system to precompile your views into the output assembly - no more .aspx or .cshtml files!
In ASP.NET MVC (using the WebForms view-engine) I have my own base ViewPage subclass:
public class ViewPage2<TViewModel,TViewData> : ViewPage<TModel> where TViewData : ViewDataDictionary<TModel> {
private TViewData _data;
public new TViewData ViewData {
get {
if( _data == null ) {
_data = (TViewData)base.ViewData;
}
return _data;
}
}
}
I recently brought this over to Razor, easily done:
public abstract class WebViewPage2<TViewModel,TViewData> : WebViewPage<TModel> where TData : ViewDataDictionary<TModel> {
// same ViewData property code as above
}
In the .cshtml files you can manually specify the base-class with the #inherits razor keyword - which requires the fully-qualified concrete generic type name - but alternatively you can omit #inherits and instead specify the #model keyword, which Razor will pass as the TModel argument to WebViewPage<TModel>.
As my base-class adds a second generic type argument, I'd rather not have to type out this ins my .cshtml files:
#inherits MyNamespace.WebViewPage2<MyOtherNameSpace.Views.FooViewModel,MyOtherNameSpace.Views.FooViewData>
...but instead do this:
Web.config
<system.web.webPages.razor>
<pages pageBaseType="MyNamespace.WebViewPage2">
...
MyPage.cshtml
#model FooViewModel
#data FooViewData
But this would require extending the Razor View-engine somehow - but this is non-obvious as it requires extending the Razor parser itself so it recognises #data TViewData and passes it as the second type argument.
You can implement an inheriting ViewClass like this (be careful with the namespace, it must not be changed):
namespace System.Web.Mvc {
public abstract class WebViewPage<TViewModel, TModel> : WebViewPage<TModel> where TViewModel : class {
private TViewModel _viewModel;
public TViewModel ViewModel {
get {
if (_viewModel == null) {
throw new InvalidOperationException("No ViewModel defined for this View.");
}
return _viewModel;
}
set { _viewModel = value; }
}
public override void InitHelpers() {
base.InitHelpers();
if (this.ViewBag.ViewModel == null) {
// No ViewModel defined => set null.
}
else if (!(this.ViewBag.ViewModel is TViewModel)) {
throw new InvalidOperationException(string.Format("The specified ViewModel value is of type '{0}' and must be of type '{1}'.", this.ViewBag.ViewModel.GetType(), typeof(TViewModel)));
} else {
_viewModel = this.ViewBag.ViewModel;
}
}
}
}
Then you dont have to change the web.config or anything else and can instantiate the view like this:
#model ViewModelType, (Input?)ModelType
The typed ViewModel (Type would by ViewModelType) is then available in the view via #ViewModel.
The Model of type (Input)ModelType is still available via #Model like it always was.
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.
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.
I want to pass some parameters to my MVC UserControl like ShowTitle(bool) and the ViewData.Model.Row . How I define my usercontrol and pass them to it?
Tanx
You can use the RenderAction HtmlHelper, found in the MVC futures dll available at codeplex. In your main page
...
You need an action method on the controller with the parameters. The action then creates a ViewData for the usercontrol. The usercontrol is returned as a view with
return View("usercontrolname", model);
The .ascx file then uses the model for just the user control. The resulting HTML is rendered into the calling page.
You can define your control as
public partial class MyUserControl : System.Web.Mvc.ViewUserControl<MyUserControlViewData> {
}
public class MyUserControlViewData {
public IList<MyData> MyData { get; set; }
public string SomethingElse { get; set; }
}
After that you can create an instance of MyUserControlViewData classin your controller, populate with data and pass it to the view. Is that what you're looking for?