RazorEngine - How to use a complex model view? - razorengine

Anyone have experience working with a complex model and RazorEngine?
Working on generating HTML using RazorEngine version 3.7.3, but running into issues with the complex model view we have. It seems like we should be able to use the templates to get RazorEngine to discover the SubSample below, but have not discovered the proper way to tell RazorEngine about the associated cshtml file.
In the example below we are looking to use a shared template for the SubSample class using the SubSample.cshtml file. As can be seen from the results, the class namespace (ReportSample.SubSample) is displayed rather than an HTML row of data.
We have tried implementing an ITemplateManager, but Resolve() is never called with a key asking for the SubSample. Also tried AddTemplate() on the service, but still no joy.
Here is a simplified example model to illustrate the issue:
namespace ReportSample
{
public class SubSample
{
public string Name { get; set; }
public string Value { get; set; }
}
public class SampleModel
{
public SubSample SubSample { get; set; }
}
}
SampleModel.cshtml
#using ReportSample
#*#model ReportSample.SampleModel*#
<table style="width: 7.5in" align="center">
<tr>
<td align="center">
<h1>
Sample Report
</h1>
</td>
</tr>
<tr>
<td>
<table style="width: 100%">
<tr>
<td colspan="2">
<b>Name:</b> #Model.SubSample.Name
</td>
<td colspan="2">
<b>Value:</b> #Model.SubSample.Value
</td>
</tr>
</table>
</td>
</tr>
<tr>
<td align="center">
<h1>
Sub-sample Data
</h1>
</td>
<td>
<table style="width: 100%">
#Model.SubSample
</table>
</td>
</tr>
</table>
SubSample.cshtml
#model ReportSample.SubSample
#using FSWebportal.Infrastructure.Mvc;
<tr class="observation-row">
<td class="observation-label">
#model.Name
</td>
<td class="observation-view">
#model.Value
</td>
</tr>
Basic RazorEngine calls:
private void html_Click(object sender, EventArgs e)
{
var gen = new RazorEngineGenerator();
var cshtmlTemplate = File.ReadAllText("Sample.cshtml");
var sample = new SampleModel() { SubSample = new SubSample() { Name = "name", Value = "value" } };
var html = gen.GenerateHtml(sample, cshtmlTemplate);
}
public string GenerateHtml<T>(T model, string cshtmlTemplate)
{
var config = new TemplateServiceConfiguration();
using (var service = RazorEngineService.Create(config))
{
return service.RunCompile(cshtmlTemplate, "", typeof(T), model);
}
}
Sample HTML Output:
Sample Report
Name: name
Value: value
Sub-sample Data
ReportSample.SubSample

I'm sorry but I don't think I fully understand your question but I have a small idea of what you are trying to do...
I think what you want are searching for are partial templates (using #Include)!
using RazorEngine;
using RazorEngine.Templating;
using System;
namespace TestRunnerHelper
{
public class SubModel
{
public string SubModelProperty { get; set; }
}
public class MyModel
{
public string ModelProperty { get; set; }
public SubModel SubModel { get; set; }
}
class Program
{
static void Main(string[] args)
{
var service = Engine.Razor;
// In this example I'm using the default configuration, but you should choose a different template manager: http://antaris.github.io/RazorEngine/TemplateManager.html
service.AddTemplate("part", #"my template");
// If you leave the second and third parameters out the current model will be used.
// If you leave the third we assume the template can be used for multiple types and use "dynamic".
// If the second parameter is null (which is default) the third parameter is ignored.
// To workaround in the case you want to specify type "dynamic" without specifying a model use Include("p", new object(), null)
service.AddTemplate("template", #"<h1>#Include(""part"", #Model.SubModel, typeof(TestRunnerHelper.SubModel))</h1>");
service.Compile("template", typeof(MyModel));
service.Compile("part", typeof(SubModel));
var result = service.Run("template", typeof(MyModel), new MyModel { ModelProperty = "model", SubModel = new SubModel { SubModelProperty = "submodel"} });
Console.WriteLine("Result is: {0}", result);
}
}
}
I have added documentation for this here: https://antaris.github.io/RazorEngine/LayoutAndPartial.html
However I think making #Model.SubSample work is possible as well, you can either change the SubSample property to be of type TemplateWriter or make the SubSample class implement IEncodedString. But I think you should consider partial templates first!

Related

Loop of checkboxes to add objects to model list

I have a model (called plan) which one of its properties is a list (ICollection) of Exercises (which is another model):
public int Id { get; set; }
public virtual ICollection<Exercise> Exercises { get; set; }
So i tried to create a view that creates a plan and another view to add Exercises to the plan from exercises in the database.
So i did a loop that ranges all the exercises from the DB and for each one i added a check box with and id same as the id of the exercise and i thought i could do something with it but i tried so many things and ways and i couldn't even sent the input of the check boxes to the controller.
I must say that i'm kinda new with programming with mvc and i tried to look all over the internet and i didn't really know people who knows to program so this is really my last chance to solve it. Sorry if it was a long post or too easy for you to even comment but i really need this.
This is very specific for my project and what i stacked on was to sent the input to the controller but i'm open to different solutions cause i'm desperate.
public async Task<ActionResult> AddExercises(string id,int[] selectedExercises)
{
List<Exercise> list = new List<Exercise>();
foreach (int i in selectedExercises)
{
list.Add(db.Exercises.Find(i));
}
db.Plans.Find(id).Exercises = list;
await db.SaveChangesAsync();
return RedirectToAction("index");
}
I am sure its wrong and the view i tried is:
#using (Html.BeginForm("index", "Plans"))
{
#Html.AntiForgeryToken()
foreach (var i in Model.Exercises)
{
<table class="table">
<tr>
<td>#Html.DisplayFor(modelItem => i.Level)</td>
<td>#Html.DisplayFor(modelItem => i.Description)</td>
<td>#Html.DisplayFor(modelItem => i.MoreDescription)</td>
<td>
#Html.ActionLink("Edit", "Edit", new { id = i.Id }) |
#Html.ActionLink("Details", "Details", new { id = i.Id }) |
<input type="checkbox" class="selectedObjects" id="i.id" /> |
</td>
</tr>
</table>
}
#Html.ActionLink("Finish", "AddExercises")
}
I use viewModel PlanExercise in this view
Your checkboxes do not have either a name attribute or a value attribute so there is nothing to submit. You html needs to be
<input type="checkbox" name="selectedExercises" value="#i.id" />
and then in order to submit the form, you need a submit button (remove #Html.ActionLink("Finish", "AddExercises"))
<input type="submit" value="Save" />
and the form needs to be (assuming the controller is FinishController)
#using (Html.BeginForm("AddExercises", "Finish"))
and the method needs to be marked with the [HttpPost] attribute.

Displaying model data in view, check for null value

I'm not sure if this is possible in the view or if I would somehow have to check for it in the controller but I have some values that are null and I'm trying to fill up a table but I would like a placeholder such as N/A to show up if there's no values.
In my controller I'm just return the model data of a basic linq query.
var model = from u in db.Users where u.Username == "Bob" select u;
In my view I'm just simply displaying the data in a table
<table class='table table-striped table-bordered table-responsive'>
<tbody>
<tr>
<td>#Html.DisplayFor(model => model.CallNo)</td>
</tr>
</tbody>
</table>
you can do something like this in your model:
[DisplayFormat(NullDisplayText = "PLACEHOLDER_VALUE")]
public string CallNo { get; set; }
Doing this you can just use
#Html.DisplayFor(model => model.CallNo)
as usual.

Wicket Custom Pagination

i have been trying to trying to implement something like
<< < (textbox) of (totalnumberofpages) > >>
any suggestions on this
Thanks in advance...
If you are looking for pagination in DataView then ,all you need to do to enable paging is to call setItemsPerPage(int) on the dataview.
Check following example JAVA code
public class RepeatingPage extends BasePage
{
private static final long serialVersionUID = 1L;
/**
* Constructor
*/
public RepeatingPage()
{
Iterator<Contact> contacts = new ContactDataProvider().iterator(0, 10);
RepeatingView repeating = new RepeatingView("repeating");
add(repeating);
int index = 0;
while (contacts.hasNext())
{
AbstractItem item = new AbstractItem(repeating.newChildId());
repeating.add(item);
Contact contact = contacts.next();
item.add(new ActionPanel("actions", new DetachableContactModel(contact)));
item.add(new Label("contactid", String.valueOf(contact.getId())));
item.add(new Label("firstname", contact.getFirstName()));
item.add(new Label("lastname", contact.getLastName()));
item.add(new Label("homephone", contact.getHomePhone()));
item.add(new Label("cellphone", contact.getCellPhone()));
final int idx = index;
item.add(AttributeModifier.replace("class", new AbstractReadOnlyModel<String>()
{
private static final long serialVersionUID = 1L;
#Override
public String getObject()
{
return (idx % 2 == 1) ? "even" : "odd";
}
}));
index++;
}
}
}
HTML code
<wicket:extend xmlns:wicket="http://wicket.apache.org">
<br/><br/>
<table cellspacing="0" class="dataview">
<tr>
<th>Actions</th>
<th>ID</th>
<th>First Name</th>
<th>Last Name</th>
<th>Home Phone</th>
<th>Cell Phone</th>
</tr>
<tr wicket:id="repeating">
<td><span wicket:id="actions">[actions]</span></td>
<td><span wicket:id="contactid">[contactid]</span> </td>
<td><span wicket:id="firstname">[firstname]</span></td>
<td><span wicket:id="lastname">[lastname]</span></td>
<td><span wicket:id="homephone">[homephone]</span></td>
<td><span wicket:id="cellphone">[cellphone]</span></td>
</tr>
</table>
</wicket:extend>
If you need pagination in listView then check for PageableListView

manipulate XML document with linq

I have the following xml that i need to transfom to an other form. I have a C# code that does it but it has a bug with is hard to track. I beleive Linq could offer a more error prone way to do this.
input xml:
<NewDataSet>
<Table>
<RoleId>5</RoleId>
<Code>DP</Code>
<Description>Process data</Description>
<Task>Validate indices</Task>
<TaskId>12</TaskId>
<Country>BE</Country>
<CountryId>3</CountryId>
</Table>
<Table>
<RoleId>5</RoleId>
<Code>DP</Code>
<Description>Process data</Description>
<Task>calculate indices</Task>
<TaskId>11</TaskId>
<Country>US</Country>
<CountryId>4</CountryId>
</Table>
<Table>
<RoleId>5</RoleId>
<Code>DP</Code>
<Description>Process data</Description>
<Task>Calculate indices</Task>
<TaskId>11</TaskId>
<Country>UK</Country>
<CountryId>5</CountryId>
</Table>
<Table>
<RoleId>1</RoleId>
<Code>DR</Code>
<Description>View data</Description>
<Task>View Reports</Task>
<TaskId>9</TaskId>
<Country>SC</Country>
<CountryId>17</CountryId>
</Table>
<Table>
<RoleId>1</RoleId>
<Code>DR</Code>
<Description>View data</Description>
<Task>View Basics</Task>
<TaskId>10</TaskId>
<Country>SC</Country>
<CountryId>17</CountryId>
</Table>
<Table>
<RoleId>1</RoleId>
<Code>DR</Code>
<Description>View data</Description>
<Task>Download data</Task>
<TaskId>11</TaskId>
<Country>FR</Country>
<CountryId>15</CountryId>
</Table>
</NewDataSet>
and the output that i need is as follow:
<NewDataSet>
<Table>
<RoleId>5</RoleId>
<Code>DP</Code>
<Description>Process data</Description>
<Task>Validate indices,Calculate indices,</Task>
<TaskId>12,11</TaskId>
<Country>BE,US,UK</Country>
<CountryId>3,4,5</CountryId>
</Table>
<Table>
<RoleId>1</RoleId>
<Code>DR</Code>
<Description>Process data from commercial fisheries</Description>
<Task>View Reports,View Basics,View data</Task>
<TaskId>9,10,11</TaskId>
<Country>SC,FR</Country>
<CountryId>17,15</CountryId>
</Table>
</NewDataSet>
As you can see, the elements are group by RoleId, Code and Description.
I have created a custum object to project the xml element to
public class Table
{
public int RoleId {get;set;}
public string Code {get;set;}
public string Description {get;set;}
public string Task {get;set;}
public int TaskId {get;set;}
public string Country {get;set;}
public int CountryId {get;set;}
}
The idea is then to use this list of custum object to recreate an xml document. But i was thinking there could be a more straigforward way. without the need to use the custum object list.
The rest of the element are simply concatenated. I hope someone have an idea sugestion of how this could be achieved using Linq to XML.
many thanks in advance
var doc = XDocument.Load("Input.txt");
var tables = from t in doc.Root.Elements("Table")
select new Table
{
RoleId = (int)t.Element("RoleId"),
Code = (string)t.Element("Code"),
Description = (string)t.Element("Description"),
Task = (string)t.Element("Task"),
TaskId = (int)t.Element("TaskId"),
Country = (string)t.Element("Country"),
CountryId = (int)t.Element("CountryId")
};
var groups = tables.GroupBy(x => new { x.RoleId, x.Code, x.Description });
var resultDoc = new XDocument(
new XElement("NewDataSet",
from g in groups
select new XElement("Table",
new XElement("RoleID", g.Key.RoleId),
new XElement("Code", g.Key.Code),
new XElement("Description", g.Key.Description),
new XElement("Task", string.Join(",", g.Select(x => x.Task))),
new XElement("TaskId", string.Join(",", g.Select(x => x.TaskId.ToString()))),
new XElement("Country", string.Join(",", g.Select(x => x.Country))),
new XElement("CountryId", string.Join(",", g.Select(x => x.CountryId.ToString()))))));
It's fully LINQ to XML solution, although you should consider changing the parsing part with XML Serialization.
Result XML:
<NewDataSet>
<Table>
<RoleID>5</RoleID>
<Code>DP</Code>
<Description>Process data</Description>
<Task>Validate indices,calculate indices,Calculate indices</Task>
<TaskId>12,11,11</TaskId>
<Country>BE,US,UK</Country>
<CountryId>3,4,5</CountryId>
</Table>
<Table>
<RoleID>1</RoleID>
<Code>Data Reader</Code>
<Description>View data</Description>
<Task>View Reports,View Basics,Download data</Task>
<TaskId>9,10,11</TaskId>
<Country>SC,SC,FR</Country>
<CountryId>17,17,15</CountryId>
</Table>
</NewDataSet>

Viewmodel IEnumerable property is empty

I'm in the middle of making an ASP .NET MVC4 based app. I'm a complete newb in that field. The idea is quite simple - have a some members in DB, show them listed, select desired ones via check boxes and redirect to some other controller which would do something with the previously selected members.
Problem is passing the list of members from View to the Controller. I've thought it would work with ViewModel. It certainly works from Controller to the View, but not the other way.
My ViewModel:
public class MembersViewModel
{
public IEnumerable<Directory_MVC.Models.Member> MembersEnum { get; set; }
public string Test { get; set; }
}
Snippet of my Controller:
public class MembersController : Controller
{
private MainDBContext db = new MainDBContext();
public ActionResult Index()
{
var model = new Directory_MVC.ViewModels.MembersViewModel();
// populating from DB
model.MembersEnum = db.Members.Include(m => m.Group).Include(m => m.Mother).Include(m => m.Father);
model.Test = "abc";
return View(model);
}
[HttpPost]
public ActionResult GoToSendEmail(Directory_MVC.ViewModels.MembersViewModel returnedStruct)
{
if (ModelState.IsValid)
{
// it is valid here
return Redirect("http:\\google.com");
}
}
Snippet of my View:
#model Directory_MVC.ViewModels.MembersViewModel
#{
ViewBag.Title = "Members listing";
var lineCount = 0;
string lineStyle;
}
#using (Html.BeginForm("GoToSendEmail", "Members", FormMethod.Post))
{
<table>
#foreach (var item in Model.MembersEnum)
{
lineCount++;
// set styling
if (lineCount % 2 == 1)
{
lineStyle = "odd-line";
}
else
{
lineStyle = "even-line";
}
<tr class="#lineStyle">
<td>
#Html.EditorFor(modelItem => item.Selected)
</td>
<td>
#Html.DisplayFor(modelItem => item.FirstName)
</td>
<td>
#Html.DisplayFor(modelItem => item.LastName)
</td>
<td>
#Html.DisplayFor(modelItem => item.Mother.FirstName) #Html.DisplayFor(modelItem => item.Mother.LastName)
</td>
<td>
#Html.DisplayFor(modelItem => item.Father.FirstName) #Html.DisplayFor(modelItem => item.Father.LastName)
</td>
<!-- other print-outs but not all properties of Member or Mother/father are printed -->
</tr>
}
</table>
<input type="submit" value="Send E-mail" />
}
The data are shown OK in the View. However, when I submit that form the returnedStruct.MembersEnum and Test string are both null in the Controller's method GoToSendEmail.
Is there a mistake or is there another possible way how to pass that members structure and check their Selected property?
Model binding to a collection works a little differently. Each item has to have an identifier so that inputs don't all have the same name. I've answered a similar question here.
#for (int i = 0; i < Model.MembersEnum.Count(); i++)
{
#Html.EditorFor(modelItem => modelItem.MembersEnum[i].FirstName)
}
...which should render something like...
<input type="text" name="MembersEnum[0].FirstName" value="" />
<input type="text" name="MembersEnum[1].FirstName" value="" />
<input type="text" name="MembersEnum[2].FirstName" value="" />
...which should then populate the collection in your ViewModel when picked up by the controller...
public ActionResult GoToSendEmail(ViewModels.MembersViewModel model)
As mentioned in the other answer, I'd have a look at some related articles from Scott Hansleman and Phil Haack.
You also mentioned that your string called Test is null when you submit to your POST action. You haven't added a field for this property anywhere within your form, so there's nothing for the model binder to bind to. If you add a field for it within your form then you should see the value in the POST action:
#Html.EditorFor(modelItem => modelItem.Test)
Html.BeginCollectionItem() helper did the job - BeginCollectionItem.

Resources