URL Routing in NOP Commerce - nopcommerce

I am trying to figure out how this works, for example if I pull up a category page: http://localhost:15536/desktops it hits the following Action method in the CatalogController:
public virtual IActionResult Category(int categoryId, CatalogPagingFilteringModel command)
So how it's passing in the correct category id (int) but it's not part of the URL how does this work?

In the Nopcommerce, there is an entity named UrlRecord, you can find it in Nop.Core\Domain\Seo\UrlRecord.cs:
public partial class UrlRecord : BaseEntity
{
public int EntityId { get; set; }
public string EntityName { get; set; }
public string Slug { get; set; }
public bool IsActive { get; set; }
public int LanguageId { get; set; }
}
The EntityName, indicates that this UrlRecord is using for which entity (i.g. Product or Category, etc.). The EntityId indicates that the Id of pointed entity (i.g. Id of Product). The Slug indicates that with what URL we can reach the intended entity. In your example, EntityName is "Category", EntityId is the Id of the category and Slug is "desktop".
So, how Nopcommerce routes these Slugs to the right Controller and Action? For figuring out of this, we have to look at the GenericPathRoute class that is located in Nop.Web.Framework\Seo\GenericPathRoute.cs. This class is registered as a custom Route to the IRouteBuilder. Regardless of asp.net core routing and Nopcommerce details, the RouteAsync method of GenericPathRoute is called at the start of every request. If we take a look into this method, we could see this section (before this section, urlRecord is fetched from the database by the Slug, so we know what is the entity and its Id, so we can route it to the desired Controller and Action with right arguments):
var currentRouteData = new RouteData(context.RouteData);
switch (urlRecord.EntityName.ToLowerInvariant())
{
case "product":
currentRouteData.Values[NopPathRouteDefaults.ControllerFieldKey] = "Product";
currentRouteData.Values[NopPathRouteDefaults.ActionFieldKey] = "ProductDetails";
currentRouteData.Values[NopPathRouteDefaults.ProductIdFieldKey] = urlRecord.EntityId;
currentRouteData.Values[NopPathRouteDefaults.SeNameFieldKey] = urlRecord.Slug;
break;
case "producttag":
currentRouteData.Values[NopPathRouteDefaults.ControllerFieldKey] = "Catalog";
currentRouteData.Values[NopPathRouteDefaults.ActionFieldKey] = "ProductsByTag";
currentRouteData.Values[NopPathRouteDefaults.ProducttagIdFieldKey] = urlRecord.EntityId;
currentRouteData.Values[NopPathRouteDefaults.SeNameFieldKey] = urlRecord.Slug;
break;
case "category":
currentRouteData.Values[NopPathRouteDefaults.ControllerFieldKey] = "Catalog";
currentRouteData.Values[NopPathRouteDefaults.ActionFieldKey] = "Category";
currentRouteData.Values[NopPathRouteDefaults.CategoryIdFieldKey] = urlRecord.EntityId;
currentRouteData.Values[NopPathRouteDefaults.SeNameFieldKey] = urlRecord.Slug;
break;
case "manufacturer":
currentRouteData.Values[NopPathRouteDefaults.ControllerFieldKey] = "Catalog";
currentRouteData.Values[NopPathRouteDefaults.ActionFieldKey] = "Manufacturer";
currentRouteData.Values[NopPathRouteDefaults.ManufacturerIdFieldKey] = urlRecord.EntityId;
currentRouteData.Values[NopPathRouteDefaults.SeNameFieldKey] = urlRecord.Slug;
break;
case "vendor":
currentRouteData.Values[NopPathRouteDefaults.ControllerFieldKey] = "Catalog";
currentRouteData.Values[NopPathRouteDefaults.ActionFieldKey] = "Vendor";
currentRouteData.Values[NopPathRouteDefaults.VendorIdFieldKey] = urlRecord.EntityId;
currentRouteData.Values[NopPathRouteDefaults.SeNameFieldKey] = urlRecord.Slug;
break;
case "newsitem":
currentRouteData.Values[NopPathRouteDefaults.ControllerFieldKey] = "News";
currentRouteData.Values[NopPathRouteDefaults.ActionFieldKey] = "NewsItem";
currentRouteData.Values[NopPathRouteDefaults.NewsItemIdFieldKey] = urlRecord.EntityId;
currentRouteData.Values[NopPathRouteDefaults.SeNameFieldKey] = urlRecord.Slug;
break;
case "blogpost":
currentRouteData.Values[NopPathRouteDefaults.ControllerFieldKey] = "Blog";
currentRouteData.Values[NopPathRouteDefaults.ActionFieldKey] = "BlogPost";
currentRouteData.Values[NopPathRouteDefaults.BlogPostIdFieldKey] = urlRecord.EntityId;
currentRouteData.Values[NopPathRouteDefaults.SeNameFieldKey] = urlRecord.Slug;
break;
case "topic":
currentRouteData.Values[NopPathRouteDefaults.ControllerFieldKey] = "Topic";
currentRouteData.Values[NopPathRouteDefaults.ActionFieldKey] = "TopicDetails";
currentRouteData.Values[NopPathRouteDefaults.TopicIdFieldKey] = urlRecord.EntityId;
currentRouteData.Values[NopPathRouteDefaults.SeNameFieldKey] = urlRecord.Slug;
break;
default:
//no record found, thus generate an event this way developers could insert their own types
EngineContext.Current.Resolve<IEventPublisher>()
?.Publish(new CustomUrlRecordEntityNameRequestedEvent(currentRouteData, urlRecord));
break;
}
context.RouteData = currentRouteData;
We can see here that it changes the routeData of this request, regarding EntityName. We can see here that this feature is going to work only for entities named in this switch/case command.

Related

Old ASP.NET code works on one computer, not on another?

So in my global.asax, I've got the following code:
Inventory.BusinessTier bt = new Inventory.BusinessTier();
string UserLogin = bt.ExtractLogin (Request.ServerVariables ["AUTH_USER"]);
Inventory.User myUser = new Inventory.User (UserLogin);
Session ["User"] = myUser;
It works just fine on one development PC, but using the same version of Visual Studio, it craps out on the third line with this error:
System.TypeInitializationException: 'The type initializer for
'Inventory.DataTier' threw an exception.'
Inner Exception
NullReferenceException: Object reference not set to an instance of an
object.
Other than a line adding impersonation in my web.config (it has to be there now), I haven't changed a single thing. Is there a way to get more info on this? I can't even trace it, because if I put a debug line in the User object constructor, it never hits it. I'm at a bit of a loss. Would appreciate any advice.
EDIT to answer questions below:
InventoryUser is a very simple user object that reads the current from the database and stores some basic user info in properties, such as UserID, Role, RoleID, and IsAdmin.
The DataTier class is a class that interacts with the database. It is used in multiple projects, so I'm quite sure it's not the problem. I tried to paste in the code anyway, but it exceeded the limit for a post.
I'm reasonably sure the problem is related to the user class. It's short, so I can paste it in here:
using System;
using System.Data;
// This is the user business object. It contains information pertaining to the current user of the application. Notably, it
// contains the department ID, which determines what inventory items the user will see when using the application. Only
// specified employees with admin access can see all items for all departments, and that is determined by a specific department ID.
namespace Inventory {
public class User {
private Guid _UserID;
private Guid _RoleID;
private Guid _UserDepartmentID;
private string _UserRole = "";
private string _UserName = "";
private bool _IsAuthorizedUser = false;
private bool _IsAdmin = false;
// Attribute declarations
public Guid UserID {
get {
return _UserID;
}
set {
_UserID = value;
}
}
public string UserRole {
get {
return _UserRole;
}
set {
_UserRole = value;
}
}
public Guid RoleID {
get {
return _RoleID;
}
set {
_RoleID = value;
}
}
public string UserName {
get {
return _UserName;
}
set {
_UserName = value;
}
}
public Guid UserDepartmentID {
get {
return _UserDepartmentID;
}
set {
_UserDepartmentID = value;
}
}
public bool IsAdmin {
get {
return _IsAdmin;
}
set {
_IsAdmin = value;
}
}
public bool IsAuthorizedUser {
get {
return _IsAuthorizedUser;
}
set {
_IsAuthorizedUser = value;
}
}
// -----------------
// - Constructor -
// -----------------
public User (string UserLogin) {
string ShortUserLogin = ExtractLogin (UserLogin);
GetUser (ShortUserLogin);
}
// ------------------
// - ExtractLogin -
// ------------------
public string ExtractLogin (string Login) {
// The domain and "\" symbol must be removed from the string, leaving only the user name.
int pos = Login.IndexOf (#"\");
return Login.Substring (pos + 1, Login.Length - pos - 1);
}
// -------------
// - GetUser -
// -------------
// This method is called to fill the user object based on the user's login. It ultimately gets authorized user data
// from the user table.
public void GetUser (string UserName) {
DataTier dt1 = new DataTier();
DataTable dt = dt1.GetUserInfo (UserName);
int RecordCount = dt.Rows.Count;
switch (RecordCount) {
case 1: // There is one user name match, as there should be. This is the likely situation.
DataRow dr = dt.Rows[0];
UserID = (Guid)dr ["UserID"];
UserRole = (string)dr ["UserRole"];
RoleID = (Guid)dr ["RoleID"];
this.UserName = UserName;
UserDepartmentID = (Guid)dr ["DepartmentID"];
IsAdmin = (bool)dr ["IsAdmin"];
IsAuthorizedUser = true;
break;
case 0: // There are no user name matches (unauthorized use).
IsAdmin = false;
IsAuthorizedUser = false;
break;
default: // There are multiple user name matches (problem!).
IsAdmin = false;
IsAuthorizedUser = false;
break;
}
}
}
}

How to pull through Row Version values from a SQLite in-memory database

I am currently implementing a Database collection/fixture for my unit tests, as documented on this question here:
xUnit.net - run code once before and after ALL tests
However, instead of using an InMemory Database, I'm using SQLite as InMemory currently has a bug in .Net Core 2.1 which doesn't do a sequence check when using a byte array type
Which leads me to my current predicament, namely that the byte array when you set up a database fixture doesn't get pulled through to the unit test when the context is pulled from the Database Fixture and into the unit test, which is causing concurrency errors when I try to run the tests.
As an example:
Fist set the DatabaseFixture class like so:
public class DatabaseFixture : IDisposable
{
public DatabaseFixture()
{
var connectionStringbuilder = new SqliteConnectionStringBuilder{DataSource = ":memory:", Cache = SqliteCacheMode.Shared};
var connection = new SqliteConnection(connectionStringbuilder.ToString());
options = new DbContextOptionsBuilder<CRMContext>()
.UseSqlite(connection)
.EnableSensitiveDataLogging()
.Options;
using (var context = new CRMContext(options))
{
context.Database.OpenConnection();
context.Database.EnsureCreated();
context.Persons.AddRange(persons);
context.SaveChanges();
}
}
public DbContextOptions<CRMContext> options { get; set; }
public void Dispose()
{
using (var context = new CRMContext(options))
{
context.Database.CloseConnection();
context.Dispose();
}
}
private IQueryable<Person> persons = new List<Person>()
{
new Person
{
Id = 1,
Forename = "Test",
Surname = "User",
RowVersion = new byte[0]
},
new Person
{
Id = 2,
Forename = "Another",
Surname = "Test",
RowVersion = new byte[0]
}
}.AsQueryable();
}
Setup your empty DatabaseCollection class as per the first link:
[CollectionDefinition("Database collection")]
public class DatabaseCollection : ICollectionFixture<DatabaseFixture>
{
}
Then set up your unit test to use this Database Fixture:
[Collection("Database collection")]
public class PersonTests : BaseTests
{
private readonly DatabaseFixture _fixture;
public PersonTests(DatabaseFixture fixture)
{
_fixture = fixture;
}
[Fact]
public void SaveAndReturnEntityAsync_SaveNewPerson_ReturnsTrue()
{
{
using (var context = new Context(_fixture.options))
{
var existingperson = new Person
{
Id = 2,
Forename = "Edit",
Surname = "Test",
RowVersion = new byte[0]
};
var mapperConfig = new MapperConfiguration(cfg => { cfg.AddProfile(new InitializeAutomapperProfile()); });
var AlertAcknowledgeService = GenerateService(context);
//Act
//var result = _Person.SaveAndReturnEntityAsync(mappedPersonAlertAcknowledge);
//Assert
Assert.Equal("RanToCompletion", result.Status.ToString());
Assert.True(result.IsCompletedSuccessfully);
Assert.Equal("The data is saved successfully", result.Result.SuccessMessage);
}
}
Now when I debug this, it hits the fixture correctly, and you can when you expand the Results view, the RowVersion variable is assigned correctly:
However, when the data is passed into the unit test, the row version gets set to null:
Any help on this would be greatly appreciated!

How to get a Json Response after saving a data in ASP.Net

how can I get a response after I created a data? So I want is when is saves. it show it's response, maybe in messagebox? Is it possible do it?
This is my controller code in saving..
[HttpPost]
[ValidateAntiForgeryToken]
public async System.Threading.Tasks.Task<ActionResult> Create(FormCollection formCollection, string fn, string ln , ParentModel apsp)
{
string username = "sa";
string apiKey = "sa";
string baseUrl = "https://sandbox-api.paysimple.com";
var settings = new PaySimpleSdk.Models.PaySimpleSettings(apiKey, username, baseUrl);
var paymentService = new PaymentService(settings);
fn = apsp.Customer.FirstName;
ln = apsp.Customer.LastName;
string street1 = apsp.Customer.BillingAddress.StreetAddress1;
string street2 = apsp.Customer.BillingAddress.StreetAddress2;
string city = apsp.Customer.BillingAddress.City;
Enum statecode = apsp.Customer.BillingAddress.StateCode;
Enum country = apsp.Customer.BillingAddress.Country;
string zipcode = apsp.Customer.BillingAddress.ZipCode;
string credit = apsp.CreditCard.CreditCardNumber;
string expir = apsp.CreditCard.ExpirationDate;
Enum issuer = apsp.CreditCard.Issuer;
decimal amount = apsp.Payment.Amount;
string ccv = apsp.Payment.Cvv;
var customerPayment = new NewCustomerPayment<CreditCard>
{
Customer = new Customer()
{
FirstName = fn,
LastName = ln,
BillingAddress = new Address
{
StreetAddress1 = street1,
StreetAddress2 = street2,
City = city,
StateCode = (StateCode)statecode,
Country = (CountryCode)country,
ZipCode = zipcode
}
},
Account = new CreditCard
{
CreditCardNumber = credit,
ExpirationDate = expir,
Issuer = (Issuer)issuer
},
Payment = new Payment
{
Amount = amount,
Cvv = ccv
}
};
var newCustomerPayment = await paymentService.CreateNewCustomerPaymentAsync(customerPayment);
return RedirectToAction("Index");
}
The function of creating a data is from the SDK and the model itself
You have 2 options:
Create action should return JsonResult instead of redirect. But you will need to use AJAX when calling Create action.:
public async System.Threading.Tasks.Task<ActionResult> Create(FormCollection formCollection, string fn, string ln , ParentModel apsp)
{
/// your code for creating object
return Json(newCustomerPayment, JsonRequestBehavior.AllowGet);
}
on the client side use Ajax.BeginForm instead of Html.BeginForm
using (Ajax.BeginForm("Create", Home , new AjaxOptions { HttpMethod = "POST", OnSuccess = "customerPaymentCreatedSuccess" }))
{
}
<script>
function customerPaymentCreatedSuccess(response)
{
alert(JSON.stringify(response, null, 4));
}
</script>
Use Post/Redirect/Get pattern. Once new payment is created, store it in TempData
and return redirect as you currently do:
TempData["newCustomerPayment"] = newCustomerPayment;
return RedirectToAction("Index");
Then in Index Action check if there is anything in TempData and if there is, pass it to the view
public ActionResult Index()
{
var customerPayment = TempData["newCustomerPayment"] as NewCustomerPayment;
if(customerPayment != null)
{
ViewBag.customerPayment = customerPayment;
}
//other code..
}
Index view - generate JavaScript code to display customerPayment:
#{var customerPayment = ViewBag.customerPayment as NewCustomerPayment;}
#if(customerPayment != null)
{
<script>
alert("#Html.Raw(JsonConvert.SerializeObject(customerPayment ))");
</script>
}
To show an feedback of your of your operation you could return a JsonResult from your method and make the call and create the message box browser-side via Javascript.
Actions to take
1)change ActionResult to JsonResult in method definition
public async System.Threading.Tasks.Task<JsonResult> Create(FormCollection formCollection, string fn, string ln , ParentModel apsp)
2)change the return to something like:
return this.Json(message)
3)make your call to the method using ajax an create the message box on the callback method

How do I create a dynamic Linq query to fill an ASP.NET databound ListView?

I am having some trouble figuring out the right way to go about creating a dynamic query that I can use values from DropDownList controls to filter and sort/order the results of a database query to fill a ListView. I am able to hard code individual queries, which works ok, except for the fact that it takes an incredible amount of effort, and is not easily changed.
My code is as follows (using all filters):
queryResult = From product In myEntities.InventoryProducts
Where product.VendorID = ddlFilterVendor.SelectedValue And product.ItemType = ddlItemType.SelectedValue And product.LabelSize = ddlLabelSize.SelectedValue And product.PrintLabel = boolPrint And product.Edited = boolEdited
Order By product.ID Ascending
Select product
Return queryResult
Is there a better method to this? I would like to be able to select the value from each DropDownList and generate a custom WHERE clause, as well as an ORDER BY clause.
Any help would be greatly appreciated, thanks.
I can give you a simple example as to how to to proceed with your idea. I am sure if you look through StackOverflow or search via google you will get code that does a better job of dynamic expression building. The same concept can be used for order by.
void Main()
{
var ops = new List<Ops>
{
new Ops
{
OperandType = typeof(string),
OpType=OpType.Equals,
OperandName = "Name",
ValueToCompare = "MM" // in your case this will be the values from the dropdowns
},
new Ops
{
OperandType = typeof(int),
OpType=OpType.Equals,
OperandName = "ID",
ValueToCompare = 1
},
};
var testClasses = new List<TestClass>
{
new TestClass { ID =1, Name = "MM", Date = new DateTime(2014,12,1)},
new TestClass { ID =2, Name = "BB", Date = new DateTime(2014,12,2)}
};
// this will produce prop => ((prop.Name == "MM") And (prop.ID == 1))
var whereDelegate = ExpressionBuilder.BuildExpressions<TestClass>(ops);
foreach(var item in testClasses.Where(whereDelegate))
{
Console.WriteLine("ID " +item.ID);
Console.WriteLine("Name " +item.Name);
Console.WriteLine("Date" + item.Date);
}
}
// Define other methods and classes here
public enum OpType
{
Equals
}
public class Ops
{
public Type OperandType {get; set;}
public OpType OpType {get; set;}
public string OperandName {get;set;}
public object ValueToCompare {get;set;}
}
public class TestClass
{
public int ID {get;set;}
public string Name {get; set;}
public DateTime Date {get;set;}
}
public class ExpressionBuilder
{
public static Func<T,bool> BuildExpressions<T>( List<Ops> opList)
{
Expression currentExpression= null;
var parameterExpression = Expression.Parameter(typeof(T), "prop");
for(int i =0; i< opList.Count; i++)
{
var op = opList[i];
Expression innerExpression = null;
switch(op.OpType)
{
case OpType.Equals :
{
var propertyExpression = Expression.Property(parameterExpression ,
op.OperandName);
var constExpression = Expression.Constant(op.ValueToCompare);
innerExpression = Expression.Equal(propertyExpression,
constExpression);
break;
}
}
if (i >0)
{
currentExpression = Expression.And(currentExpression, innerExpression);
}
else
{
currentExpression = innerExpression;
}
}
var lambdaExpression = Expression.Lambda<Func<T,bool>>(currentExpression,
new []{parameterExpression });
Console.WriteLine(lambdaExpression);
return lambdaExpression.Compile() ;
}
}

Part of ASP.NET MVC application data save not being applied

I am writing an MVC application, and I wanted to do some extra formatting so the phone numbers all are stored the same. To accomplish this I made a simple external function to strip all non-numeric characters and return the formatted string:
public static string FormatPhone(string phone)
{
string[] temp = { "", "", "" };
phone = Regex.Replace(phone, "[^0-9]","");
temp[0] = phone.Substring(0, 3);
temp[1] = phone.Substring(3, 3);
temp[2] = phone.Substring(6);
return string.Format("({0}) {1}-{2}", temp[0], temp[1], temp[2]);
}
There is also regex in place in the model to make sure the entered phone number is a valid one:
[Required(ErrorMessage = "Phone Number is required.")]
[DisplayName("Phone Number:")]
[StringLength(16)]
[RegularExpression("^\\(?([0-9]{3})\\)?[-. ]?([0-9]{3})[-. ]?([0-9]{4})$",
ErrorMessage = "Please enter a valid phone number.")]
public object phone { get; set; }
This is what I did in the controller:
[HttpPost]
public ActionResult Edit(int id, FormCollection collection)
{
var customer = customerDB.Customers.Single(c => c.id == id);
try
{
customer.phone = HelperFunctions.FormatPhone(customer.phone);
UpdateModel(customer,"customer");
customerDB.SaveChanges();
return RedirectToAction("Index");
}
catch
{
var viewModel = new CustomerManagerViewModel
{
customer = customerDB.Customers.Single(c => c.id == id)
};
return View(viewModel);
}
}
When I step through this, the string updates then resets back to the format it was before being ran through the function. Also, any of the other fields update with no problem.
Any ideas? Thanks in advance.
Your UpdateModel call is overwriting the customer field. Try swapping these two lines of code:
try
{
UpdateModel(customer,"customer"); <--
customer.phone = HelperFunctions.FormatPhone(customer.phone); <--
customerDB.SaveChanges();
return RedirectToAction("Index");
}

Resources