I have an Emailer class I am using via Dependency Injection to send emails which gets the contents of a View to send in an email. The process I have works great UNLESS the view contains a call to the underlying URL helper, such as using an A tag like this:
<a asp-controller="Project" asp-action="List">Open</a>
Here is the code I am using to render a view into a string:
private string renderViewAsString<TModel>(string folder, string viewName, TModel model)
{
var httpContext = new DefaultHttpContext { RequestServices = _serviceProvider };
var actionContext = new ActionContext(httpContext, new RouteData(), new ActionDescriptor());
var viewEngineResult = _viewEngine.FindView(actionContext, folder + "/" + viewName, false);
var view = viewEngineResult.View;
var viewData = new ViewDataDictionary<TModel>(new EmptyModelMetadataProvider(), new ModelStateDictionary());
viewData.Model = model;
var tempData = new TempDataDictionary(httpContext, _tempDataProvider);
using (var output = new StringWriter())
{
var viewContext = new ViewContext(actionContext, view, viewData, tempData, output, new HtmlHelperOptions());
var task = view.RenderAsync(viewContext);
task.Wait();
return output.ToString();
}
}
_serviceProvider is of type IServiceProvider and _viewEngine is of type IRazorViewEngine which are both injected in the constructor.
If it references the URL helper it produces this exception at the task.Wait() line:
Index was out of range. Must be non-negative and less than the size of the collection.
Parameter name: index
with this as the call stack:
at System.ThrowHelper.ThrowArgumentOutOfRangeException(ExceptionArgument argument, ExceptionResource resource)
at System.Collections.Generic.List`1.get_Item(Int32 index)
at Microsoft.AspNetCore.Mvc.Routing.UrlHelper.get_Router()
at Microsoft.AspNetCore.Mvc.Routing.UrlHelper.GetVirtualPathData(String routeName, RouteValueDictionary values)
at Microsoft.AspNetCore.Mvc.Routing.UrlHelper.Action(UrlActionContext actionContext)
at Microsoft.AspNetCore.Mvc.UrlHelperExtensions.Action(IUrlHelper helper, String action, String controller, Object values, String protocol, String host, String fragment)
at Microsoft.AspNetCore.Mvc.ViewFeatures.DefaultHtmlGenerator.GenerateActionLink(ViewContext viewContext, String linkText, String actionName, String controllerName, String protocol, String hostname, String fragment, Object routeValues, Object htmlAttributes)
at Microsoft.AspNetCore.Mvc.TagHelpers.AnchorTagHelper.Process(TagHelperContext context, TagHelperOutput output)
at Microsoft.AspNetCore.Razor.TagHelpers.TagHelper.ProcessAsync(TagHelperContext context, TagHelperOutput output)
at Microsoft.AspNetCore.Razor.Runtime.TagHelpers.TagHelperRunner.<RunAsync>d__0.MoveNext()
How do I get around this without having to resort to hard-coding the A element or email contents?
I was able to get it to work. The call stack mentioned not finding a router, so it was a matter of providing it:
First I added this as a DI object in the constructor parameters:
IHttpContextAccessor accessor
And this in the constructor:
_context = accessor.HttpContext;
Then I changed the function to this:
private string renderViewAsString<TModel>(string folder, string viewName, TModel model)
{
var actionContext = new ActionContext(_context, new RouteData(), new ActionDescriptor());
var viewEngineResult = _viewEngine.FindView(actionContext, folder + "/" + viewName, false);
var view = viewEngineResult.View;
var viewData = new ViewDataDictionary<TModel>(new EmptyModelMetadataProvider(), new ModelStateDictionary());
viewData.Model = model;
var tempData = new TempDataDictionary(_context, _tempDataProvider);
using (var output = new StringWriter())
{
var viewContext = new ViewContext(actionContext, view, viewData, tempData, output, new HtmlHelperOptions());
viewContext.RouteData = _context.GetRouteData(); //set route data here
var task = view.RenderAsync(viewContext);
task.Wait();
return output.ToString();
}
}
This is the one I use in ASP.NET Core 2.0
using System;
using System.Collections.Generic;
using System.IO;
using System.Linq;
using Microsoft.AspNetCore.Http;
using Microsoft.AspNetCore.Mvc;
using Microsoft.AspNetCore.Mvc.Abstractions;
using Microsoft.AspNetCore.Mvc.ModelBinding;
using Microsoft.AspNetCore.Mvc.Razor;
using Microsoft.AspNetCore.Mvc.Rendering;
using Microsoft.AspNetCore.Mvc.ViewEngines;
using Microsoft.AspNetCore.Mvc.ViewFeatures;
using Microsoft.AspNetCore.Routing;
namespace Website
{
public class RazorViewToStringRenderer
{
private readonly IHttpContextAccessor accessor;
private readonly IRazorViewEngine viewEngine;
private readonly IServiceProvider serviceProvider;
private readonly ITempDataProvider tempDataProvider;
public RazorViewToStringRenderer(
IHttpContextAccessor accessor,
IRazorViewEngine viewEngine,
IServiceProvider serviceProvider,
ITempDataProvider tempDataProvider)
{
this.accessor = accessor;
this.viewEngine = viewEngine;
this.serviceProvider = serviceProvider;
this.tempDataProvider = tempDataProvider;
}
public string RenderViewToString<TModel>(string viewLocation, TModel model)
{
HttpContext httpContext = accessor.HttpContext;
httpContext.RequestServices = serviceProvider;
ActionContext actionContext = new ActionContext(httpContext, new RouteData(), new ActionDescriptor());
IView view = FindView(actionContext, viewLocation);
using (StringWriter stringWriter = new StringWriter())
{
ViewDataDictionary<TModel> viewDataDictionary = new ViewDataDictionary<TModel>(
new EmptyModelMetadataProvider(),
new ModelStateDictionary());
viewDataDictionary.Model = model;
TempDataDictionary tempDataDictionary = new TempDataDictionary(
actionContext.HttpContext,
tempDataProvider);
HtmlHelperOptions htmlHelperOptions = new HtmlHelperOptions();
ViewContext viewContext = new ViewContext(
actionContext,
view,
viewDataDictionary,
tempDataDictionary,
stringWriter,
htmlHelperOptions);
viewContext.RouteData = accessor.HttpContext.GetRouteData();
view.RenderAsync(viewContext).Wait();
return stringWriter.ToString();
}
}
private IView FindView(ActionContext actionContext, string viewLocation)
{
ViewEngineResult getViewResult = viewEngine.GetView(null, viewLocation, true);
if (getViewResult.Success)
{
return getViewResult.View;
}
ViewEngineResult findViewResult = viewEngine.FindView(actionContext, viewLocation, true);
if (findViewResult.Success)
{
return findViewResult.View;
}
IEnumerable<string> searchedLocations = getViewResult.SearchedLocations.Concat(findViewResult.SearchedLocations);
string message = string.Join(
Environment.NewLine,
new[] { $"Unable to find view '{viewLocation}'. The following locations were searched:" }.Concat(searchedLocations)); ;
throw new Exception(message);
}
}
}
Remember in Startup.cs -> public void ConfigureServices(IServiceCollection serviceCollection) to add this
serviceCollection.AddSingleton<RazorViewToStringRenderer>();
I have no direct answer to this, because i have the exact same problem. But i made a Github issue yesterday. https://github.com/aspnet/Entropy/issues/170
Edit: The solution of Rono works for me
I had the same error, but I couldn't use Rono's solution because it required a real HttpContext, which I did not have in my command-line application. I managed to fix it by adding a RouteCollection to my ActionContext:
private ActionContext GetActionContext()
{
var httpContext = new DefaultHttpContext { RequestServices = serviceProvider };
var actionContext = new ActionContext(httpContext, new RouteData(), new ActionDescriptor());
MapRoutes(actionContext);
return actionContext;
}
private void MapRoutes(ActionContext actionContext)
{
var routes = new RouteBuilder(new ApplicationBuilder(serviceProvider))
{
DefaultHandler = new DefaultHandler()
};
routes.MapRoute(
name: "default",
template: "{controller=Home}/{action=Index}/{id?}"
);
actionContext.RouteData.Routers.Add(routes.Build());
}
Full source code with an example project on github: https://github.com/waf/RazorToStringExample/blob/master/RazorToStringExample/Services/RazorViewToStringRenderer.cs
I have tried so many different ways to display this contents of the hashmap based on a key and i would like to be told if im doing it the wrong way please?
session.setAttribute("AvailableLessons", availableLessons.getLessons());
<c:forEach var="temp" items="${sessionScope.AvailableLessons}">
<tbody>
<tr>
<form action="" method="POST">
<td>
<c:out value="${temp['description']}"/>
</td>
<td>
Bean code:
public class LessonTimetable implements Serializable {
private Connection connection = null;
private ResultSet rs = null;
private PreparedStatement st = null;
private Map lessons = new HashMap<String, List<Lesson>>();
private DataSource ds = null;
public Lesson less;
public LessonTimetable() {
// You don't need to make any changes to the try/catch code below
try {
// Obtain our environment naming context
Context initCtx = new InitialContext();
Context envCtx = (Context) initCtx.lookup("java:comp/env");
// Look up our data source
ds = (DataSource) envCtx.lookup("jdbc/LessonDatabase");//change to LessonDatabase..will also have to setup credentials for my virtualmin server account.
} catch (Exception e) {
System.out.println("Exception message is " + e.getMessage());
}
try {
Class.forName("com.mysql.jdbc.Driver");
connection = DriverManager.getConnection("jdbc:mysql://localhost:3306/test", "root", "");
try {
if (connection != null) {
// TODO instantiate and populate the 'lessons' HashMap by selecting the relevant infromation from the database
List<String> putDescriptions = new ArrayList<String>();
List<String> putDates = new ArrayList<String>();
List<String> putStartTime = new ArrayList<String>();
List<Integer> Level = new ArrayList<Integer>();
List<String> LessonID = new ArrayList<String>();
List<String> endTime = new ArrayList<String>();
String query = String.format("SELECT description,level,startDateTime,endDateTime,lessonid FROM LESSONS");
st = connection.prepareStatement(query);
rs = st.executeQuery();
connection.setAutoCommit(false);
st.setFetchSize(0);
while (rs.next()) {
String getDescription = rs.getString("description");
int level = rs.getInt("level");
Timestamp startDate = rs.getTimestamp("startDateTime");
Timestamp endDate = rs.getTimestamp("endDateTime");
String LessonId = rs.getString("lessonid");
this.less = new Lesson(getDescription, startDate, endDate, level, LessonId);
putDescriptions.add(less.description);
putStartTime.add(less.startTime);
endTime.add(less.endTime);
List list = Arrays.asList(less.date.split("2010"));
for (int i = 0; i < list.size(); i++) {
putDates.add(list.get(i).toString());
Level.add(less.level);
LessonID.add(less.ID);
this.lessons.put("description", putDescriptions);
this.lessons.put("StartDate", putDates);
this.lessons.put("StartTime", putStartTime);
this.lessons.put("EndTime", endTime);
this.lessons.put("Level", Level);
this.lessons.put("LessonID", LessonID);
If I understand correctly, availableLessons.getLessons()returns a Map, containing "description" as a key.
Your code starts with session.setAttribute("AvailableLessons", availableLessons.getLessons());. So the attribute AvailableLessonscontains a Map.
So, all you need to access the value associated to the key "description" in that Map is
${AvailableLessons['description']}
No need for a loop, just like in Java you would only need
availableLessons.get("description")
to access this value, without any need for a loop.
I am Implementing the service to service communication i.e. IIS wcf service and wcf hosted in Windows service
one WCF service hosted in Windows service
consume above service in windows application i.e. Exe which will have one access db behind it e.g. AccessDB1
then one wcf service hosted in IIS which will have its own access db e.g.
AccessDB2
so at the end there are 2 Accessdbs, from exe on some button click transfer some data from DB2 to DB.
then do the vice versa thing that transfer data from AccessDB1 to AccessDB2.
How to do that in this way . i have tried this code
Created Iservice1 Interface and declare two methods
[ServiceContract]
public interface IService1
{
[OperationContract]
string InsertUserDetails(UserDetails userInfo);
[OperationContract]
DataSet SelectUserDetails();
}
Implements this Methods in Service1.svc Class
public class Service1 : IService1
{
// string connetionString = null;
OleDbConnection cnn;
string connetionString = #"Provider=Microsoft.ACE.OLEDB.12.0;Data Source=D:\Anand\Database\UserDB.accdb";
public DataSet SelectUserDetails()
{
OleDbConnection cnn;
cnn = new OleDbConnection(connetionString);
cnn.Open();
OleDbCommand cmd = new OleDbCommand("select UserID,UserName,Country,Email from
UserRecord order by UserId ", cnn);
OleDbDataAdapter da = new OleDbDataAdapter(cmd);
DataSet ds = new DataSet();
da.Fill(ds);
cmd.ExecuteNonQuery();
cnn.Close();
return ds;
}
public string InsertUserDetails(UserDetails userInfo)
{
string Message;
OleDbConnection cnn;
cnn = new OleDbConnection(connetionString);
cnn.Open();
string str = "insert into UserRecord values('"+userInfo.UserID+"','" +
userInfo.UserName + "','" + userInfo.Password + "','" + userInfo.Country +
"','" + userInfo.Email + "') ";
OleDbCommand cmd = new OleDbCommand(str, cnn);
try
{
int result = cmd.ExecuteNonQuery();
if (result == 1)
{
Message = userInfo.UserName + " Details inserted successfully";
}
else
{
Message = userInfo.UserName + " Details not inserted successfully";
}
cnn.Close();
return Message;
}
catch (Exception)
{
throw;
}
}
After that create Windows Service and given Service Reference into Windows Service and Create the Service Reference Object
public partial class UserRegistration : Form
{
ServiceReference1.Service1Client service = new ServiceReference1.Service1Client();
public UserRegistration()
{
InitializeComponent();
showdata();
}
private void showdata() // to show the data in the DataGridView
{
DataSet ds = new DataSet();
ds = service.SelectUserDetails();
dataGridView1.DataSource = ds.Tables[0];
dataGridView1.AutoResizeColumns(DataGridViewAutoSizeColumnsMode.AllCells);
}
private void button1_Click(object sender, EventArgs e)
{
ServiceReference1.UserDetails objuserdetail = new ServiceReference1.UserDetails(); // add type reference
objuserdetail.UserID = Int32.Parse(txtuserId.Text);
objuserdetail.UserName = txtUserName.Text;
objuserdetail.Password = txtPass.Text;
objuserdetail.Country = txtCountry.Text;
objuserdetail.Email = txtEmailId.Text;
service.InsertUserDetails(objuserdetail);
showdata();
Empty();
}
public void Empty()
{
txtCountry.Text = "";
txtEmailId.Text = "";
txtPass.Text = "";
txtuserId.Text = "";
txtUserName.Text = "";
}
}
I would like to write code to add a DataColumn to a DataTable, but when I save the DataTable, it does not include the new DataColumn.
It saves any new DataRows I add, but not the DataColumns.
Can somebody please tell me what I am doing wrong?
public partial class Form1 : Form
{
MyDatabase DB;
DataTable Products;
public Form1()
{
InitializeComponent();
}
private void Form1_Load(object sender, EventArgs e)
{
DB = new MyDatabase();
DB.Open(#"C:\Users\Grant\Documents\Database.accdb");
Products = DB.GetTable("Products");
AddColumn();
AddRow();
DB.Save(Products);
}
private void AddColumn()
{
DataColumn Column = new DataColumn();
Column.DataType = Type.GetType("System.String");
Column.ColumnName = "TestColumn";
Products.Columns.Add(Column);
}
private void AddRow()
{
DataRow Row;
Row = Products.Rows.Add(1, "B", "C");
}
}
class MyDatabase
{
// The following program has to be installed on the computer
// http://www.microsoft.com/downloads/en/details.aspx?familyid=7554F536-8C28-4598-9B72-EF94E038C891&displaylang=en
private String provider = "Microsoft.ACE.OLEDB.12.0";
private String source;
private OleDbConnection connection;
private String connectionString;
private DataSet dataSet = new DataSet();
private OleDbDataAdapter adapter;
private OleDbCommandBuilder commandBuilder;
public String Provider
{
get { return provider; }
set { provider = value; }
}
public String Source
{
get { return Source; }
set { source = value; }
}
public void Open(String Filename)
{
connectionString = #"Provider=" + provider + #";Data Source=" + Filename;
connection = new OleDbConnection(connectionString);
connection.Open();
adapter = new OleDbDataAdapter();
}
public void BuildStrings()
{
commandBuilder = new OleDbCommandBuilder(adapter);
adapter.UpdateCommand = commandBuilder.GetUpdateCommand();
adapter.InsertCommand = commandBuilder.GetInsertCommand();
adapter.DeleteCommand = commandBuilder.GetDeleteCommand();
}
public DataTable GetTable(String TableName)
{
adapter.SelectCommand = new OleDbCommand("SELECT * From " + TableName, connection);
BuildStrings();
adapter.Fill(dataSet, TableName);
return dataSet.Tables[TableName];
}
public void Save(DataTable Table)
{
adapter.Update(Table);
adapter.Update(dataSet, "Products");
}
}
Got an answer from a different forum.
You can not add new column/field to database table using dataset or datatable you might need to use "ALTER TABLE" with ADO.NET commands. Check below links
How Can I Insert New Column Into A Database Table Using SqlDataAdapter and DataTable?[^]
adding a column to a SQL table in VB using ADO.NET commands[^]
I am developing a web application using MVC 3. This application connects to an SQL Server database through ASMX Web Services. Each Web Method calls a Stored Procedure and returns a DataTable.
This is the code I'm using to call the Stored Procedure:
public static DataTable ExecSP(string StoredProcedureName, List<string> ParameterNames, List<Object> ParameterValues)
{
SqlConnection Connection = new SqlConnection(ConfigurationManager.ConnectionStrings["SQLServer"].ConnectionString);
SqlDataReader Reader = null;
DataTable SPResult = null;
try
{
Connection.Open();
SqlCommand Command = new SqlCommand("dbo." + StoredProcedureName, Connection);
Command.CommandType = CommandType.StoredProcedure;
if (ParameterNames != null)
{
for (int i = 0; i < ParameterNames.Count; i++)
{
SqlParameter Parameter = new SqlParameter(ParameterNames[i], ParameterValues[i]);
if (Parameter.SqlDbType.Equals(SqlDbType.NVarChar))
{
Parameter.SqlDbType = SqlDbType.VarChar;
}
if (Parameter.SqlValue == null)
{
Parameter.SqlValue = DBNull.Value;
}
Command.Parameters.Add(Parameter);
}
}
Reader = Command.ExecuteReader();
SPResult = new DataTable();
SPResult.Load(Reader);
}
catch (Exception ex)
{
throw;
}
finally
{
Connection.Close();
if (Reader != null)
{
Reader.Close();
}
}
return SPResult;
}
I would like to know if there is a straight-forward way to convert this DataTable into a Model that can then be passed to a View (like, for example, the model binding that happens in an AJAX post) and, if there isn't, what are the alternatives. I know that using LINQ would probably solve this problem, but I can't use it.
Thanks in advance.
Best regards.
Found a solution.
I built a generic method that translates any DataTable into a List of whatever class I specify:
public static List<T> Translate<T>(DataTable SPResult, Func<object[],T> del)
{
List<T> GenericList = new List<T>();
foreach (DataRow Row in SPResult.Rows)
{
GenericList.Add(del(Row.ItemArray));
}
return GenericList;
}
where del is a delegate. When calling this method, del should be the constructor of the specified class. Then, in all Model classes, I built a constructor that receives an object[] RowFromTable
public class MyClass
{
public int ID { get; set; }
public string Description { get; set; }
public FormaProcesso(object[] RowFromTable)
{
this.ID = (int)RowFromTable[0];
this.Description = RowFromTable[1].ToString();
}
}
Finally, to put it all together, this is what happens when I call the Web Method:
public List<MyClass> GetAll()
{
DataTable SPResult = MyWebService.GetAll().Table;
return Translate<MyClass>(SPResult, l => new MyClass(l));
}
Got the idea from here