I am using more then one repeater on same page. But when I use Execute reader for 2nd repeater then it gives exception that there is already execute reader running.. so close it. I put ExecuteReader(CommandBehavior.CloseConnection) but it give error that command behaviour doesn't exists... Any idea about this issue?
You need to explicitly close the DataReader if you specify that CommandBehavior, it will not do it for you.
http://msdn.microsoft.com/en-us/library/y6wy5a0f.aspx
Personally I would bind UI controls to strongly typed objects. So for example I would define a Product model:
public class Product
{
public int Id { get; set; }
public string Name { get; set; }
}
then a method to read products from the database:
public static IEnumerable<Product> GetProducts()
{
using (var conn = new SqlConnection("Some connection string"))
using (var cmd = conn.CreateCommand())
{
conn.Open();
cmd.CommandText = "SELECT prod_id, prod_name FROM products";
using (var reader = cmd.ExecuteReader())
{
while (reader.Read())
{
yield return new Product
{
Id = reader.GetInt32(reader.GetOrdinal("prod_id")),
Name = reader.GetString(reader.GetOrdinal("prod_name")),
};
}
}
}
}
and in the web tier I would call this method to fetch my products and bind them to some UI controls:
protected void Page_Load(object sender, EventArgs e)
{
var products = Db.GetProducts().ToArray();
repeater1.DataSource = products;
repeater2.DataSource = products;
gridView.DataSource = products;
...
}
And when you get sick of writing those SQL queries you might take a look at an ORM, such as Entity Framework for example.
Related
Is there any Core 2.1 sample available for using SignalR with SQLDependency.
Did enable broker, etc. but never get any dependency onChange event firing. Just the event subscribe is triggered.
When the MS-SQL database table Cities changes on the back-end, I want to see the change reflected right-away on the client web page without having to refresh/reload the page.
//start the dependency when app start in ConfigureServices
SqlDependency.Start(Configuration.GetConnectionString("DefaultConnection"));
using Microsoft.AspNetCore.SignalR;
using SignalR_Test4.Data;
using SignalR_Test4.Hubs;
using System.Collections.Generic;
using System.Data.SqlClient;
namespace SignalR_Test4.Models
{
public class CityRepository
{
private readonly ApplicationDbContext _context;
private readonly IHubContext<CityHub> _hubcontext;
public CityRepository(ApplicationDbContext context, IHubContext<CityHub> hubcontext)
{
_context = context;
_hubcontext = hubcontext;
}
public IEnumerable<City> GetCities()
{
List<City> listOf = new List<City>();
//listOf = _context.Cities;
using (var conn = new SqlConnection(GlobalVar.connectionString))
{
conn.Open();
using (var cmd = new SqlCommand(#"SELECT * FROM Cities", conn))
{
cmd.Notification = null;
SqlDependency dependency = new SqlDependency(cmd);
dependency.OnChange += Dependency_OnChange;
if (conn.State == System.Data.ConnectionState.Closed)
conn.Open();
var reader = cmd.ExecuteReader();
while (reader.Read())
{
listOf.Add(new City { Id = (string)reader["Id"], Name_en = (string)reader["name_en"], CountryId = (string)reader["CountryId"], Code = (string)reader["Code"] });
}
}
}
return listOf;
}
private void Dependency_OnChange(object sender, SqlNotificationEventArgs e)
{
if (e.Type == SqlNotificationType.Change)
{
_hubcontext.Clients.All.SendAsync("GetCities");
}
}
}
}
The issue was within the line:
var cmd = new SqlCommand(#"SELECT Id, Name_en, CountryId, Code from [dbo].Cities", conn)
It is required to use the field name (Not the *) and also the 2 part table name convention => [dbo].Cities
Very basic explanation: I have created a "User" class in a ConnectionClass.cs file and need to use it elsewhere (see below, it's a login button).
The code I'm trying to type is: `
namespace Vehicle_Website.Pages.Account
{
public partial class Login : System.Web.UI.Page
{
protected void Page_Load(object sender, EventArgs e)
{
}
protected void btnLogin_Click(object sender, EventArgs e)
{
User user = ConnectionClass.LoginUser(txtLogin.Text, txtPassword.Text);
if (user != null)
{
//Store login variables to session
Session["login"] = user.Name;
Session["type"] = user.Type;
Response.Redirect("~/Pages/Home.aspx");
}
else
{
lblError.Text = "Login Failed";
}
}
}
}`
The "User" should be highlighting in "blue" if you like, recognizing that it's an already created class elsewhere, except is isn't and I' getting an error
"type or namespace "User" could not be found"
The exact same applies to the "ConnectionClass" line.. It should be highlighting but again says not recognized.
I have created a public class User and a public static User LoginUser(string login, string password) elsewhere and they work fine without errors. I cant understand why, in plain English, the words "User" and "ConnectionClass" are not "highlighting/changing colour" and being recognised.
I have tried changing properties to compile but seems to have done nothing.
Whatever other information you need I'll be happy to share.
UPDATE***
This is my ConnectionClass.cs file (which is working fine):
namespace Vehicle_Website.App_Code
{
public static class ConnectionClass
{
private static SqlConnection conn;
private static SqlCommand command;
static ConnectionClass()
{
string connectionString = ConfigurationManager.ConnectionStrings["DataConnection"].ToString();
conn = new SqlConnection(connectionString);
command = new SqlCommand("", conn);
}
public static User LoginUser(string login, string password)
{
//Check if user exists
string query = string.Format("SELECT COUNT (*) FROM WebsiteDB.dbo.users WHERE name = '{0}'", login);
command.CommandText = query;
try
{
conn.Open();
int amountOfUsers = (int)command.ExecuteScalar();
if(amountOfUsers == 1)
{
//User exists, check if passwords match
query = string.Format("SELECT password FROM users WHERE name = '{0}'", login);
command.CommandText = query;
string dbPassword = command.ExecuteScalar().ToString();
if(dbPassword == password)
{
//Passwords match, retrieve further information.
query = string.Format("SELECT email, user_type FROM users WHERE name = '{0}'", login);
command.CommandText = query;
SqlDataReader reader = command.ExecuteReader();
User user = null;
while (reader.Read())
{
string email = reader.GetString(0);
string type = reader.GetString(1);
user = new User(login, password, email, type);
}
return user;
}
else
{
//Passwords do not match.
return null;
}
}
else
{
//User exists
return null;
}
}
finally
{
conn.Close();
}
}
}
}
UPDATE
So i think i may have found the problem but no idea how to fix:
Basically my login.aspx.cs page isnt being "linked" to my ConnectionClass.cs
By this, i mean, I have created the below line on the login.aspx.cs page :
Session["login"] = user.Name;
When i try and use ".Name" on my ConnectionClass.cs file, it isnt recognised :
user.Name);
Any idea on how to ensure both pages are communicating? I have tried correcting the namespace so they both match with no luck...
Do you have your ConnectionClass in the same project as the login button? If not you have to add a reference to the project that contains the ConnectionClass.
Also, is your ConnectionClass static? If it is not you will not be able to use it without creating first an instance of a class like this:
ConnectionClass myClass = new ConnectionClass();
User user = myClass.LoginUser(txtLogin.Text, txtPassword.Text);
You have to use full name of the class . That is ConnectedClass.User when accessing the nested class. By default nested class is private so also make sure you are using the correct access modifier
See to that the namespace Vehicle_Website.App_Code is included
in the aspx.cs which contains the button click method
Clearly see this line
public static class ConnectionClass
As per the code it is a static class and also see this constructor!
static ConnectionClass()
{
string connectionString = ConfigurationManager.ConnectionStrings["DataConnection"].ToString();
conn = new SqlConnection(connectionString);
command = new SqlCommand("", conn);
}
When is the constructor called during instanciating an object. Is
it possible instanciate the ConnectionClass? As per OOPs
programming concepts, you cannot do it. Then how will the connection
string and command will be assigned!!
Try fixing this first.
I have DataReader that holds the results from a stored procedure caal. The results consist of two fields ...
UserID
UserName
Normally I bind these results to an ASP.NET dropdownlist control ...
ddlUserList.DataSource = rdr // rdr is the DataReader
ddlUserList.DataTextField = "UserName"
ddlUserList.DataValueField = "UserID"
ddlUserList.DataBind()
However I am now trying to accomplish the same thing using jQuery AJAX. What I am stuck on is how to manually convert the dataset held in the DataReader to JSON. How are multiples values separated? Does this look correct?
{{"UserID":1, "UserName":"Bob"}, {"UserID":2, "UserName":"Sally"},{"UserID":3, "UserName":"Fred"}}
I realize there are libraries out there such as JSON.NET to handle the serialization but I am in the learning stage now and want to make sure I understand everything from the bottom up.
Was wondering if you have tried using System.Web.Script.Serialization.JavaScriptSerializer library?
You can look at Rick Stahl's blog on this:
http://www.west-wind.com/weblog/posts/737584.aspx
Or you could also do something like create a method that will pull out data from the datareader and place it in a list of objects. (See code below). These list of object will be serialized using the JavaScriptSerializer library.
Hope this helps!
public class User
{
public int UserId { get; set; }
public string UserName { get; set;}
}
public class DataLayer
{
public string GetUsers(string connString)
{
string result = null;
List<User> users = null;
// get data using SqlReader
using(var conn = new SqlConnection(connString))
{
using(var cmd = new SqlCommand{ Connection = conn, CommandText = "SELECT * FROM Users", CommandType = CommandType.Text })
{
conn.Open();
var reader = cmd.ExecuteReader(CommandBehavior.CloseConnection);
if(!reader.HasRows)
return null;
//convert data reader to a list of user objects
users = (List<User>)ConvertToList<User>(ref reader);
conn.Close();
}
}
//convert list of objects in list to json objects
var jsonSerializer = new System.Web.Script.Serialization.JavaScriptSerializer();
result = jsonSerializer.Serialize(users);
return result;
}
public static IList<T> ConvertToList<T>(ref SqlDataReader reader)
{
IList<T> result = null;
if (reader.IsClosed)
return result;
result = new List<T>();
T item = default(T);
while (reader.Read())
{
//create item instance
item = (T)Activator.CreateInstance<T>();
//get class property members
var propertyItems = item.GetType().GetProperties();
//populate class property members with data from data reader
for (int ctr = 0; ctr < reader.FieldCount; ctr++)
{
if(reader.GetName(ctr) == propertyItems[ctr].Name)
propertyItems[ctr].SetValue(item, UtilsHelper.GetValue<string>(reader[ctr]), null);
}
//add item to list
result.Add(item);
}
reader.Close();
reader.Dispose();
return result;
}
}
I want to build a form where users can enter some data in a few text boxes and click the "Add" button and have the the data appear in a grid or something like it. They need to be able to enter multiple rows.
Then when they are done they can click the "Save" button to save the data to the database.
How do I get the data from from the text boxes into the "grid"?
EDIT
Here's what I have so far
protected void Page_Load(object sender, EventArgs e)
{
DataTable myDataTable = new DataTable();
DataColumn dc1 = new DataColumn("Employee");
myDataTable.Columns.Add(dc1);
DataColumn dc2 = new DataColumn("Category");
myDataTable.Columns.Add(dc2);
DataColumn dc3 = new DataColumn("Description");
myDataTable.Columns.Add(dc3);
DataColumn dc4 = new DataColumn("P/S/N");
myDataTable.Columns.Add(dc4);
DataColumn dc5 = new DataColumn("Hours");
myDataTable.Columns.Add(dc5);
DataColumn dc6 = new DataColumn("WeekEnding");
myDataTable.Columns.Add(dc6);
}
protected void btnAddToGrid_Click(object sender, EventArgs e)
{
DataRow row = myDataTable.NewRow();// i'm getting error here sayind myDataTable does not exist
row["Employee"] = LoginName1.ToString();
row["Category"] = ddlCategory.SelectedValue;
row["Description"] = txtDescription.Text;
row["P/S/N"] = ddlPSN.SelectedValue;
row["Hours"] = ddlHours.SelectedValue;
row["WeekEnding"] = txtWeekEnding.Text;
myDataTable.Rows.Add(row);
Ok your first problem from your comment:
i'm getting error here sayind myDataTable does not exist
Is because you defined your table in the Page_Load and then it goes out of scope at the end of the function. It sounds like you don't understand the basic concepts of ASP.NET and what you are trying to do.
Here is a quick and dirty untested solution to your problem but I must stress that you should try and understand why this solution works before trying to extend it or do more in ASP.NET. I hope my 10 minutes of my time helps you get a good start into understanding C# and ASP.NET.
[Serializable]
public class YourData
{
public string Employee { get; set; }
public string Category { get; set; }
public string Description { get; set; }
public string P_S_N { get; set; }
public string Hours { get; set; }
public string WeekEnding { get; set; }
}
// Used to add your list of data to the viewstate cache
// (There are other ways to store data between posts but I am trying to keep it simple)
public void SetYourCachedData(List<YourData> data)
{
ViewState["YourDataCache"] = data;
}
// Used to get your save data so far
public List<YourData> GetYourCachedData()
{
return ViewState["YourDataCache"] == null ?
new List<YourData>() : (List<YourData>)(ViewState["YourDataCache"]);
}
protected void Page_Load(object sender, EventArgs e)
{
// Do nothing
}
protected void btnAddToGrid_Click(object sender, EventArgs e)
{
// Get the data and store it in the ViewState to cache it between post backs
// Assuming one record added with each click.
List<YourData> data = GetYourCachedData();
data.Add(new YourData()
{
Employee = LoginName1.ToString(),
Category = ddlCategory.SelectedValue,
Description = txtDescription.Text,
P_S_N = ddlPSN.SelectedValue,
Hours = ddlHours.SelectedValue,
WeekEnding = txtWeekEnding.Text
});
// You can bind to any type of collection easily.
// You don't need to use a DataTable with a GridView
yourGrid.DataSource = data;
yourGrid.DataBind();
SetYourCachedData(data);
}
protected void btnSaveData_Click(object sender, EventArgs e)
{
// Pull data from the ViewState cache
List<YourData> data = GetYourCachedData();
// Now iterate through data to save it to your database...
// look this up if you don't know how as this is a lot more work.
}
looks like you just need to set the output object's datasource and bind it.
I.e.:
myGrid.datasource = myDataTable
myGrid.databind()
And as Kelsey says -- keep your datatable in scope.
a public dim right above your page_load would be easier, if you just want to try it out. Otherwise, the separate class approach is a good way to go.
I've decided to start another thread based on the responses I got in this thread:
Asp.Net: Returning a Reader from a Class
I was returning a reader, but members have suggested I'd be better off returning a Dataset instead and also try to seperate the data access tier from the presentation tier.
This is what I have so far:
//my class methods
public DataSet GetSuppliers()
{
SqlConnection conn = new SqlConnection(connectionString);
SqlCommand cmd = new SqlCommand("con_spSuppliersList", conn);
cmd.CommandType = CommandType.StoredProcedure;
cmd.Parameters.AddWithValue("#blogid", HttpContext.Current.Request.QueryString["p"]);
return FillDataSet(cmd, "SuppliersList");
}
//my FillDataSet method
private DataSet FillDataSet(SqlCommand cmd, string tableName)
{
SqlConnection conn = new SqlConnection(connectionString);
cmd.Connection = conn;
SqlDataAdapter adapter = new SqlDataAdapter(cmd);
DataSet ds = new DataSet();
try
{
conn.Open();
adapter.Fill(ds, tableName);
}
finally
{
conn.Close();
}
return ds;
}
// on my ascx page I call the method like so:
protected void Page_Load(object sender, EventArgs e)
{
//instantiate our class
MyClass DB = new MyClass();
// grab the table of data
DataTable dt = DB.GetSuppliers().Tables["SuppliersList"];
//loop through the results
foreach (DataRow row in dt.Rows)
{
this.supplierslist.InnerHtml += Server.HtmlEncode(row["Address"].ToString()) + "<br/>";
this.supplierslist.InnerHtml += "<b>Tel: </b>" + Server.HtmlEncode(row["Telephone"].ToString()) + "<p/>";
}
}
}
Would anyone like to suggest improvements?
Is my loop 'data tier' or 'presentation tier', should the loop be inside the class and I just return a formatted string instaed of a dataset?
Thanks for all the great advice
I also would use a Typed DataSet or create your own class that holds the properties so you are not dealing with strings like row["Address"] you would say object.Address and get compile time checking.
DataSets have a lot of built in functionality that is nice but also caries with it a lot of overhead that might not be needed in something simple. Creating a simple class with properties and passing that out of your data access layer is probably the simplest way to implement what you want.
Something like this on the DAL (Data Access Layer) side:
//Also pass in the blogID dont have the DAL get the value from the UI layer..
//make the UI layer pass it in.
public IList<Supplier> GetSuppliers(string connectionString, int blogID)
{
IList<Supplier> suppliers = new List<Supplier>();
//wrap with the using statements
using (SqlConnection conn = new SqlConnection(connectionString))
{
using (SqlCommand cmd = new SqlCommand("con_spSuppliersList", conn))
{
cmd.CommandType = CommandType.StoredProcedure;
cmd.Parameters.AddWithValue("#blogid", blogID);
SqlDataReader reader = cmd.ExecuteReader();
while (reader.Read())
{
suppliers.Add(new Supplier
{
Address = reader.GetString(0),
Telephone = reader.GetString(1)
});
}
}
}
return suppliers;
}
}
public class Supplier
{
//I would have Address an object....but you have as string
//public Address Address { get; set; }
public string Address { get; set; }
public string Telephone { get; set; }
}
//Example if you went with Address class...
public class Address
{
//Whatever you want in the address
public string StreetName { get; set; }
public string Country { get; set; }
public string Region { get; set; }
public string City { get; set; }
}
One thing you should get in the habit of doing is calling Dispose() on your SqlConnection. The best pattern to do this is to use the using statement, which will automatically dispose of it for you. It looks like this:
private DataSet FillDataSet(SqlCommand cmd, string tableName)
{
using(SqlConnection conn = new SqlConnection(connectionString))
{
cmd.Connection = conn;
SqlDataAdapter adapter = new SqlDataAdapter(cmd);
DataSet ds = new DataSet();
try
{
conn.Open();
adapter.Fill(ds, tableName);
}
finally
{
conn.Close();
}
return ds;
}
}
What you have in the foreach loop in Page_Load, is presentation logic (layout), and IMO this should not be in the code-behind of your page, but in the markup.
I'd suggest that instead of using a foreach loop to construct the HTML output, you should use a databound control (such as a asp:Repeater, DataList or GridView). Then you can bind the repeater to your dataset or datatable and have all the markup where it belongs (in the ASCX file). See this page for an example.
As a general note: you can find lots of tutorials on www.asp.net, e.g. about data access: http://www.asp.net/%28S%28pdfrohu0ajmwt445fanvj2r3%29%29/learn/data-access/