I'm working on a webshop-like asp.net mvc 4 website with a wcf-service datalayer. My application is build with maincategories, subcategories and products. Each product can only be in one subcategory and my url's are like this:
/maincategoryname/subcategoryname/{productid}/producttitle
And the corresponding breadcrumb trail:
Home > Maincategory > Subcategory > Producttitle
I'm currently using MvcSitemapProvider to generate my navigation menu's and breadcrumbs. I'm loading all the url's as dynamic nodes without cache. This solution works for a couple of products but when I add 1000 products the sitemap takes 6,5 second to populate, wich is way too long.
I turned on caching in MvcSitemapProvider. This way the application loads much faster. But when a user adds a new product and navigates to this new product (page). The url is not yet in the sitemap file because it uses cache. This way my navigation and breadcrumbs are not generated.
My question is:
Is it possible to add a new node to the sitemap at runtime after a user adds a new product?
The accepted answer is now a little out of date. In MvcSiteMapProvider v4, there is no longer a GetCacheDescription() method in a DynamicNodeProvider. This didn't seem to work anyway.
You can now invalidate the cache manually by using the [SiteMapCacheRelease] attribute on the action methods that update the data:
[MvcSiteMapProvider.Web.Mvc.Filters.SiteMapCacheRelease]
[HttpPost]
public ActionResult Edit(int id)
{
// Update the record
return View();
}
Or by calling a static method:
MvcSiteMapProvider.SiteMaps.ReleaseSiteMap();
You also have the option now to extend the framework to supply your own cache dependencies.
MvcSiteMapProvider allows for Dynamic Sitemaps that solve for Cache Dependancies.
You can enable this by creating a class which implements IDynamicNodeProvider.
Below is an example that generates dynamic nodes based on a database query, and also sets up a cache dependency on that same query.
public class ProductNodesProvider : IDynamicNodeProvider
{
static readonly string AllProductsQuery =
"SELECT Id, Title, Category FROM dbo.Product;";
string connectionString =
ConfigurationManager.ConnectionStrings ["db"].ConnectionString;
/// Create DynamicNode's out of all Products in our database
public System.Collections.Generic.IEnumerable<DynamicNode> GetDynamicNodeCollection()
{
var returnValue = new List<DynamicNode> ();
using (SqlConnection connection = new SqlConnection(connectionString)) {
SqlCommand command = new SqlCommand (AllProductsQuery, connection);
connection.Open ();
SqlDataReader reader = command.ExecuteReader ();
try {
while (reader.Read()) {
DynamicNode node = new DynamicNode ();
node.Title = reader [1];
node.ParentKey = "Category_" + reader [2];
node.RouteValues.Add ("productid", reader [0]);
returnValue.Add (node);
}
} finally {
reader.Close ();
}
}
return returnValue;
}
/// Create CacheDependancy on SQL
public CacheDescription GetCacheDescription ()
{
using (SqlConnection connection = new SqlConnection(connectionString)) {
SqlCommand command = new SqlCommand (AllProductsQuery, connection);
SqlCacheDependency dependancy = new SqlCacheDependency (command);
return new CacheDescription ("ProductNodesProvider")
{
Dependencies = dependancy
};
}
}
}
While this is all very nifty - and should invalidate the cache when your customers change products in the datbase - the whole SqlCacheDependancy can be tricky and is SQL Server-Version dependent.
You may go with a custom CacheDependacy instead, if you're using the cache to store your products.
Related
I'm completely new to C# programming and I'm trying to learn on my own. Currently I'm building a mini-project to exercise.
I understand that the user layer should not have any data query for security reasons perhaps?
So I have created a separate Data Access class to retrieve data. This is what my data access class looks like(I'll be using stored procedures for better security once I learn how to use it):
public class DataAccess
{
public List<Customer> FilteredCustomersList(string name)
{
using (IDbConnection connection = new MySql.Data.MySqlClient.MySqlConnection(Helper.CnnVal("FineCreteDB")))
{
var output = connection.Query<Customer>($"SELECT * from `Customers` WHERE `Cust_Name` LIKE '{name}'").ToList();
return output;
}
}
Basically I send over a string from the user form to query the database, the data is retrieved and stored in a list. User form:
private void RetrieveData()
{
try
{
DataAccess db = new DataAccess();
filteredcustomers = db.FilteredCustomersList(CustomerNameTxtBox_AutoComplete.Text);
ntn_num = filteredcustomers.Select(x => x.Cust_NTN).ElementAt(0);
strn_num = filteredcustomers.Select(x => x.Cust_STRN).ElementAt(0);
address = filteredcustomers.Select(x => x.Cust_Address).ElementAt(0);
phone_num = filteredcustomers.Select(x => x.Cust_Phone).ElementAt(0);
id_num = filteredcustomers.Select(x => x.Cust_ID).ElementAt(0);
}
catch (Exception)
{
MessageBox.Show("Customer not found. If customer was recently added, try updating DB.", "Error", MessageBoxButtons.OK, MessageBoxIcon.Exclamation);
DataAccess db = new DataAccess();
filteredcustomers = db.AllCustomersList();
ntn_num = "";
strn_num = "";
address = "";
phone_num = "";
}
}
On the user form side, "filteredcustomers" holds the list of data sent back, now here is the problem: I use the filteredcustomers list to extract the different column values like so:
address = filteredcustomers.Select(x => x.Cust_Address).ElementAt(0);
and then use them to populate the respective textboxes like:
Address_TxtBox.Text = address;
Everything works fine, but I don't want the userform to have these queries for all individual columns, because from what I've understood so far, this is bad programming and bad for security as well.
Can anyone guide me how I can keep the values in Data Access layer and just call them into my form?
I'm sorry if this is a long post, I'm just learning and wanted to be as detailed as possible.
You're already doing everything reasonably correctly as per how Dapper is to be used. Dapper doesn't maintain a local graph of entities from the db, track changes to it and automatically save them. If you want that, use something like EF
For dapper you retrieve data with a SELECT and send it back with an UPDATE
If you're only expecting one Customer for the name, do this:
var output = connection.QueryFirstOrDefault<Customer>($"SELECT * from `Customers` WHERE `Cust_Name` LIKE #n", new { n = name });
https://dapper-tutorial.net/queryfirst
This will return just one customer instance (or null; check it!) meaning you can tidy up your form code to:
c = db.FilteredCustomer(CustomerNameTxtBox_AutoComplete.Text);
ntn_num = c?.Cust_NTN;
strn_num = c?.Cust_STRN;
And so on
Your "if customer was recently added try updating db" doesn't really make sense- the query is done live, so the db is about as up to date as it can be
I'm working on expanding our unit test suite, and I've come across a specific class that I'm attempting to figure out how to mock. I have a method that accepts a byte[] array as a parameter. In a perfect world, this byte array will always be a PDF file that contains a form of some sort. It then extracts all of the form fields from that pdf and returns them.
How can I potentially mock up logic that is dependent on file data? My only real ideas are to include the pdf in the project and use IO to read the test file, or to attempt to generate a pdf form on the fly and then extract those fields.
Here is the code for the PDF extractor:
public class PdfFormExtractor : IDisposable
{
private readonly PdfReader _pdfReader;
private readonly MemoryStream _newPdf;
private readonly PdfStamper _pdfStamper;
public PdfFormExtractor(byte[] pdf)
{
_pdfReader = new PdfReader(pdf);
_newPdf = new MemoryStream();
_pdfStamper = new PdfStamper(_pdfReader, _newPdf);
}
public FormDto ExtractForm()
{
var pdfFormFields = _pdfStamper.AcroFields;
var form = new FormDto()
{
Fields = pdfFormFields.Fields.Select(n => new FormFieldDto
{
Name = n.Key,
Label = n.Key
}).ToList()
};
return form;
}
#region IDisposable Support
// disposable implementation
#endregion
}
Use Resource files.
In Visual Studio, just create a resource file in your test project to contain all the files you want to use in your tests.
Open the resx and you will see the usual list of strings. But you're not limited to strings: you can select "Files" in the top-left dropdown and then drag and drop files INTO the resx file.
When you do, pay attention to the pasted file properties: you can select to interpret the file as binary (a byte[] is exposed, as in your use case) or text (with encoding, which exposes a string).
Then, in your test you can just reference the strongly typed Resource object and the strongly typed byte[] with the contents of your test file.
This strategy has a lot of applications when testing complex scenarios, especially when paired with a smart enough serializer/deserializer (like Json.NET).
You can serialize any complex data structure as Json, then in your tests reference it as a string (exposed directly by the Resource file's class), deserialize it with a simple JsonConvert.DeserializeObject and run your test on the business logic directly.
You can use Microsoft.Fakes to generate fake assembly for your *.dll. With Fakes, we can bend the outcome of any properties, methods,..
I faked Sqlconnection class which usually is hardened for mocking.
Right-click on your assembly (in my case, System.Data)
Create fakes assembly
It creates shims & stubs
We need to add scope by using (ShimsContext.Create()). Everything inside the scope will behave as you proposed.
public void ExtractFormTest()
{
using (ShimsContext.Create())
{
#region FakeIt
System.Data.SqlClient.Fakes.ShimSqlConnection.AllInstances.Open = (SqlConnection sqlConnection) =>
{
Console.WriteLine("Opened a session with Virtual Sql Server");
};
System.Data.SqlClient.Fakes.ShimSqlConnection.AllInstances.Close = (SqlConnection sqlConnection) =>
{
Console.WriteLine("Closed the session with Virtual Sql Server");
};
System.Data.SqlClient.Fakes.ShimSqlCommand.AllInstances.ExecuteNonQuery = (SqlCommand sqlCommand) =>
{
if (sqlCommand.CommandText.ToLower().Contains("truncate table"))
{
Console.WriteLine("Ran " + sqlCommand.CommandText + " at Virtual Sql Server");
return 1;
}
return 0;
};
System.Data.SqlClient.Fakes.ShimSqlBulkCopy.AllInstances.WriteToServerDataTable = (SqlBulkCopy sqlBulkCopy, DataTable datatable) =>
{
Console.WriteLine("Written #" + datatable.Rows.Count + " records to Virtual Sql Server");
};
System.Data.Common.Fakes.ShimDbDataAdapter.AllInstances.FillDataSet = (DbDataAdapter dbDataAdapter, DataSet dataSet) =>
{
var _dataSet = new DataSet();
var _dataTable = DataTableHelper.LoadFlatfileIntoDataTable(Path.Combine(dailyEmailFlatfilesDirectory, "Flatfile.txt"), flatfileDelimiter, flatfileDataTableFields, regexPatternMdmValidEmail, traceWriter);
if (dbDataAdapter.SelectCommand.CommandText.Equals(mdmSqlStorProcForSpFlatfileData))
{
while (_dataTable.Rows.Count > 1000)
_dataTable.Rows.RemoveAt(0);
}
else if (dbDataAdapter.SelectCommand.CommandText.Equals(mdmSqlStorProcForStFlatfileData))
{
while (_dataTable.Rows.Count > 72)
_dataTable.Rows.RemoveAt(0);
}
dataSet.Tables.Add(_dataTable);
dataSet = _dataSet;
return 1;
};
#endregion
#region Act
FormDto formDto = ExtractForm();
#endregion
#region Assert
// Upto the scope of your method and acceptance criteria
#endregion
}
}
Hope this helps!
So I have this code that checks if new data is added to online database by comparing the rows of online and local database. If new data is found it inserts the new data to local database.
public class Reservation
{
public string res_no { get; set; }
public string mem_fname { get; set; }
}
My Code :
private async void updateDineList()
{
DBconnector.OpenConnection();
//Gets data from online database
HttpClient client = new HttpClient();
var response = await client.GetStringAsync("http://example.com/Reservation/view_pending_reservation");
var persons = JsonConvert.DeserializeObject<List<Reservation>>(response);
//Gets data from Local database
string string_reservation = "SELECT res_no,mem_fname FROM res_no WHERE res_status='pending';";
DataTable reservation_table = new DataTable();
MySqlDataAdapter adapter_reservartion = new MySqlDataAdapter(string_reservation, DBconnector.Connection);
adapter_reservartion.Fill(reservation_table);
//Gets the row of each table
int local = reservation_table.Rows.Count;
int online = persons.Count;
//Compares rows of online and local database
if (local < online)
{
//if the rows of online database is greater than local database
//inserts the new data from local database
string Command_membership = "INSERT INTO reservation_details (res_no,mem_fname) VALUES (#res_no, #mem_fname);";
for (int i = local; i < online; i++)
{
//inserts new data from online to local database
using (MySqlCommand myCmd = new MySqlCommand(Command_membership, DBconnector.Connection))
{
myCmd.CommandType = CommandType.Text;
myCmd.Parameters.AddWithValue("#res_no", persons[i].res_no);
myCmd.Parameters.AddWithValue("#mem_fname", persons[i].mem_fname);
myCmd.ExecuteNonQuery();
}
}
MessageBox.Show("New Records Found");
}
else
{
MessageBox.Show("No new Records");
}
DBconnector.Connection.Close();
}
So my question is there any problem could occur with this code, it works fine but is there any way to improve this. I know MySQL replication is better but I am only using free Web Hosting with few MySQL privileges.
The clear improvement is not to create a new command for every row. You should either create the command and parameters once and then set the parameters and call for each row, or better still package the set of updates into a single structure, like and xml string, and then pass the whole lot to the database via a stored procedure call.
Other probably problematic issue is that you are checking purely based on row counts. Don't know if that is valid in your scenario but it sounds dangerous. What if rows are deleted? or is that not possible in your scenario. Some other way of checking last updates would probably be preferable.
Without more context that's about all I can see.
Im sorry if this is a duplicate question, but i did a search and was unable to find info on what i was looking for. If you know of a qusetion to refer too, please link me!
But anyways, i have a function creating a class
private Item CreateItem(string name, bool stackable, int amount, string discription)
{
Item item = new Item(name, stackable, amount, discription);
return item;
}
Then i have another function that finds the stats
private Item findItemStats(string name)
{
if (name == "Gold")
return CreateItem(name, false, 0, "Gold Bar");
return null;
}
This is what im using to add the item too the inventory
internal void addItem(string name)
{
var item = findItemStats(name);
if (item == null)
Debug.LogError("Item not found!");
Instance.itemsToAdd.Add(item);
if (!inventory())
return;
if (inventory().activeInHierarchy)
{
placeItemsOnInventory();
sortItems();
}
My question is, whats a better way to store and retrieve the data of item stats. I at one point hosted a private server and on that, the item stats were stored in a .txt (or json w/e) and then would have a class for taking that data and placing it to the item that was being called. Was just curious of a way to either do that, or a way to store the data in a separate class/file with easy access and placement of the item data.
This can be a fairly wide open topic and depends on your needs. The simplest option if you are just saving something locally is using PlayerPrefs
PlayerPrefs Example:
PlayerPrefs.SetInt("Player Score", 10);
PlayerPrefs.Save();
//And to fetch:
var playerScore = PlayerPrefs.GetInt("Player Score");
More on using PlayerPrefs
Serialization Example Snippet.
For something more complex you can serialize your data to a data format such as XML, JSON, binary, CSV or any data that you want to import.This is an example of binary.
public void SaveData()
{
if (!Directory.Exists("Saves"))
Directory.CreateDirectory("Saves");
BinaryFormatter formatter = new BinaryFormatter();
FileStream saveFile = File.Create("Saves/save.binary");
LocalCopyOfData = PlayerState.Instance.localPlayerData;
formatter.Serialize(saveFile, LocalCopyOfData);
saveFile.Close();
}
public void LoadData()
{
BinaryFormatter formatter = new BinaryFormatter();
FileStream saveFile = File.Open("Saves/save.binary", FileMode.Open);
LocalCopyOfData = (PlayerStatistics)formatter.Deserialize(saveFile);
saveFile.Close();
}
More on Saving and Loading player data
SqlLite
Alternatively you can use tooling for integrating a sqlite db into your project. The code for this looks like a standard db connection in .net.
string conn = "URI=file:" + Application.dataPath + "/PickAndPlaceDatabase.s3db"; //Path to database.
IDbConnection dbconn;
dbconn = (IDbConnection) new SqliteConnection(conn);
dbconn.Open(); //Open connection to the database.
IDbCommand dbcmd = dbconn.CreateCommand();
string sqlQuery = "SELECT value,name, randomSequence " + "FROM PlaceSequence";
dbcmd.CommandText = sqlQuery;
IDataReader reader = dbcmd.ExecuteReader();
How to Setup Sqlite withn Unity3d.
Cloud Hosting
For data that needs to be persist and be made available across multiple machines. You may want to consider hosting your data on a proper database or cloud hosted data store service. Some examples:
Unity Cloud Data is in alpha(As of 7/10/2016)
Firebase(Fun fact:Firebase was originally concieved to be a chat server tool for mmo's)
Play Fab
Game Sparks
Amazon RDS
Google Cloud Datastore(MySql)
Google Cloud Database(NoSql)
Azure Db
back4app (thanks #Joe Blow)
Other Data Storage options
Googling Backend as a service yields lots of other goodies as well. Sky's the limit!~
Unity has Scriptable objects that can be used to store data, and the objects get stored within the assets folder so easily accessible.
https://unity3d.com/learn/tutorials/modules/beginner/live-training-archive/scriptable-objects
In ServiceStack I am using the MiniProfiler configured to store profiles using SqlServerStorage. The profiles are being recorded to the database into the 'MiniProfilers' table without issue. Is there a viewer that would render the data (especially the json) from the MiniProfilers table?
This sample shows how SqlServerStorage is being initialized. The method is called from the AppHost.cs on Configure:
private void EnableProfiling(string profilerConnection)
{
using (var conn = new SqlConnection(profilerConnection))
{
conn.Open();
var miniProfilersTableExists = conn.ExecuteScalar<int>("select case when exists((select * from information_schema.tables where table_name = 'MiniProfilers')) then 1 else 0 end");
if (miniProfilersTableExists != 1)
conn.Execute(SqlServerStorage.TableCreationScript);
}
Profiler.Settings.Storage = new SqlServerStorage(profilerConnection);
}
In your HTML page (just before </HEAD>) add the following line
#ServiceStack.MiniProfiler.Profiler.RenderIncludes().AsRaw()
In Application_Start (Global.asax) add the following lines
Profiler.Settings.PopupRenderPosition = RenderPosition.Left;
Profiler.Settings.SqlFormatter = new SqlServerFormatter();
It will display a tiny tab at the top left corner of your page in which you can click to reveal more information.