Creating logs with WebApi on Azure - sql-server

I'm currently working on new project with WebApi and SQL Server, but as every application we must to generate and save logs, the question is, how to create it and the proper way to storage, since I'm using Azure there's a Azure Blob Table, which sounds perfect to create logs, but I'm not a professional. So every user that has been logged, how to organize this, I need some help!

Azure Web App provide Application log feature, we can enable it in Azure portal.
After that, we can write log using following code and logs will be written in the blobs which I configured on Azure Portal.
Trace.TraceInformation("Hello Azure Log");
If you want to write your application log to Azure table storage, you could create a custom TraceListener. I just created a AzureTableStorageTraceListener which will write trace log to Azure Table, code below is for your reference.
public class AzureTableStorageTraceListener : System.Diagnostics.TraceListener
{
protected override string[] GetSupportedAttributes()
{
return new[] { "StorageConnectionString", "LogsTableName" };
}
public override void TraceEvent(TraceEventCache eventCache, string source, TraceEventType eventType, int id, string message)
{
Write(message, eventType.ToString());
}
public override void Write(string message, string category)
{
string stroageConnectionString = Attributes["StorageConnectionString"];
string tableName = Attributes["LogsTableName"];
// Retrieve the storage account from the connection string.
CloudStorageAccount storageAccount = CloudStorageAccount.Parse(stroageConnectionString);
// Create the table client.
CloudTableClient tableClient = storageAccount.CreateCloudTableClient();
// Create the CloudTable object that represents the "people" table.
CloudTable table = tableClient.GetTableReference(tableName);
// Create a new log entity.
LogEntity log = new LogEntity(category, message);
// Create the TableOperation object that inserts the log entity.
TableOperation insertOperation = TableOperation.Insert(log);
// Execute the insert operation.
table.Execute(insertOperation);
}
public override void WriteLine(string message, string category)
{
Write(message + "\n", category);
}
public override void Write(string message)
{
Write(message, null);
}
public override void WriteLine(string message)
{
Write(message + "\n");
}
}
public class LogEntity : TableEntity
{
public LogEntity(string category, string message)
{
category = category == null ? "Default" : category;
this.PartitionKey = category;
this.RowKey = Guid.NewGuid().ToString();
this.Message = message;
this.CreatedDate = DateTime.Now;
}
public LogEntity() { }
public string Category { get; set; }
public string Message { get; set; }
public DateTime CreatedDate { get; set; }
}
To use this TraceListener, you need to add following configuration section to Web.config.
<system.diagnostics>
<trace autoflush="true">
<listeners>
<add name="AzureTableStorageListener"
type="{your namespace name}.AzureTableStorageTraceListener,{your assembly name which contains the custom trace listener}"
StorageConnectionString="{your storage connection string}"
LogsTableName="{azure storage table name}"/>
</listeners>
</trace>
</system.diagnostics>

Related

Cannot Insert new Data in the Database in .NET Core API error Database operation

I am working on an API and when I started adding new data. I received this error. It was working when I manually add the ID every input but now I got this error and after adding some solutions from here its still not working.
Error:
Microsoft.EntityFrameworkCore.DbUpdateConcurrencyException: Database operation expected to affect 1 row(s) but actually affected 0 row(s).
Data may have been modified or deleted since entities were loaded. See http://go.microsoft.com/fwlink/?LinkId=527962 for information on understanding and handling optimistic concurrency exceptions.
Code for insert:
public bool Insert(string UserName, SendInventoryModel sendInventoryModel)
{
using (DatabaseContext context = new DatabaseContext())
{
bool flag = false;
// Create new
InventoryEntity inventoryEntity = new InventoryEntity
{
UserName = sendInventoryModel.UserName,
Item = sendInventoryModel.Item ,
};
context.Table.Add(inventoryEntity);
context.SaveChanges();
// Check
var model = CheckUserNameID(UserName, sendInventoryModel.Item);
var data = context.Table.Find(model.Id);
if (null != data)
{
flag = true;
}
return flag;
}
}
SendInventoryModel:
public class SendSiteMailModel
{
[Required]
public string UserName { get; set; }
[Required]
public string Item{ get; set; }
}
InventoryController:
[HttpPost("{username}")]
[Authorize]
public JObject Post([Required] string UserName, [FromBody] SendInventoryModel sendInventoryModel)
{
ResponseModel x = new ResponseModel();
try
{
InventoryRepository InventoryRepository = new InventoryRepository();
bool isSuccess = InventoryRepository.Insert(UserName, sendInventoryModel);
}
catch (Exception error)
{
// if not successful
}
return Json(x);
}
I already added [DatabaseGenerated(DatabaseGeneratedOption.Identity)] in my InventoryEntity and InventoryModel.
InventoryEntity:
[Key]
DatabaseGenerated(DatabaseGeneratedOption.Identity)]
public int Id { get; set; }
InventoryModel:
[DatabaseGenerated(DatabaseGeneratedOption.Identity)]
public int Id { get; set; }
I also added the below code in my DBContext.cs:
public virtual DbSet<OtherTableEntity> Table{ get; set; }
public virtual DbSet<InventoryEntity> Table{ get; set; }
protected override void OnModelCreating(ModelBuilder modelBuilder)
{
modelBuilder.Entity<OtherTableEntity>();
modelBuilder.Entity<InventoryEntity>().Property(x => x.Id).ValueGeneratedOnAdd();
base.OnModelCreating(modelBuilder);
}
partial void OnModelCreatingPartial(ModelBuilder modelBuilder);
Add finally my table design: Inventory ID:
(Is Identity) = Yes
Identity Increment = 1
Identity Seed = 1
Note that there is no Primary Key in the Inventory table. And its an old table with existing data. The current database was migrated from membership to identity.
After all the things that I have added the context.SaveChanges(); in the insert method still does not work. Any ideas or suggestion on how to fix this problem?
Note: I've changed the table entity names and models per Asherguru suggestion since its kinda confusing and generic.
Are your TableEntity and Table in database same table names?
Why different names - TableEntity and Table?
Try to add [Table("YourTableNameInDatabase")] in TableEntity class. Then EF can find actual table in database and insert into this table.
[Table("YourTableNameInDatabase")]
public partial class TableEntity
{
public int Id { get; set; }
}
It would be less confusing if you show actual table names with some necessary screenshots.

Database migration adds new column Tenancy_Id when adding Tenant to User class

I use boilerplate template with Angular, .NET Framework and Module Zero. I have added Tenant to User class and User list to Tenant class
public class Tenant : AbpTenant<User>
{
public Tenant()
{
this.Users = new HashSet<User>();
}
public ICollection<User> Users { get; set; }
public Tenant(string tenancyName, string name)
: base(tenancyName, name)
{
}
}
public class User : AbpUser<User>
{
public const string DefaultPassword = "123qwe";
public virtual Tenant Tenant { get; set; }
public static string CreateRandomPassword()
{
return Guid.NewGuid().ToString("N").Truncate(16);
}
public static User CreateTenantAdminUser(int tenantId, string emailAddress, string password)
{
var user = new User
{
TenantId = tenantId,
UserName = AdminUserName,
Name = AdminUserName,
Surname = AdminUserName,
EmailAddress = emailAddress,
Password = new PasswordHasher().HashPassword(password)
};
return user;
}
}
Now when I add migration I get
public override void Up()
{
AddColumn("dbo.AbpUsers", "Tenant_Id", c => c.Int());
CreateIndex("dbo.AbpUsers", "TenantId");
CreateIndex("dbo.AbpUsers", "Tenant_Id");
AddForeignKey("dbo.AbpUsers", "Tenant_Id", "dbo.AbpTenants", "Id");
AddForeignKey("dbo.AbpUsers", "TenantId", "dbo.AbpTenants", "Id");
}
Why entity framework creates another foreign key and how should I add users list to Tenant?
I'm surprised you could add a migration. I got this while trying to reproduce:
Unable to determine the relationship represented by navigation property 'User.Tenant' of type 'Tenant'. Either manually configure the relationship, or ignore this property using the '[NotMapped]' attribute or by using 'EntityTypeBuilder.Ignore' in 'OnModelCreating'.
I got it to work by adding an InverseProperty attribute:
public class Tenant : AbpTenant<User>
{
[InverseProperty("Tenant")]
public ICollection<User> Users { get; set; }
}

asp.net identity authentication for simple registration -- asp.net mvc

I am about to start a project that needs custom identity authetication and authorization.
For authentication I am asking this question.
I need to know how can I create ExampleDBContext class that extends from DBContext class? and what settings I need to do in Startup.Auth.cs to make it work. And for registration, what should be the name of the registration table and what should be its columns? and what other tables and their columns need to be there?
There is so much info related to identity authentication but nothing actually useful. thanks
I'm not sure but I think you want something like this:
ExampleDbContext.cs
public class ExampleDbContext : IdentityDbContext<User>, IExampleDbContext
{
public ExampleDbContext()
: base("yourConnectionStringName", throwIfV1Schema: false)
{
}
public static ExampleDbContext Create()
{
return new ExampleDbContext();
}
protected override void OnModelCreating(DbModelBuilder modelBuilder)
{
base.OnModelCreating(modelBuilder);
modelBuilder.Entity<User>().ToTable("Users");
modelBuilder.Entity<IdentityRole>().ToTable("Roles");
modelBuilder.Entity<IdentityUserRole>().ToTable("UserRoles");
modelBuilder.Entity<IdentityUserLogin>().ToTable("UserLogins");
modelBuilder.Entity<IdentityUserClaim>().ToTable("UserClaims");
}
void IDisposable.Dispose()
{
this.Dispose();
}
}
You can use the code first method to generate the tables. That means that you can customize your User model the way you want. Here is the example of
User.cs
public class User : IdentityUser
{
public async Task<ClaimsIdentity> GenerateUserIdentityAsync(UserManager<User> manager)
{
// Note the authenticationType must match the one defined in CookieAuthenticationOptions.AuthenticationType
var userIdentity = await manager.CreateIdentityAsync(this, DefaultAuthenticationTypes.ApplicationCookie);
// Add custom user claims here
return userIdentity;
}
public string FirstName { get; set; }
public string LastName { get; set; }
}
In the Users you can see that I've added some custom fields (FirstName LastName) and if you use the code first you can generate the custom Users table and use it for authentication. No need to add anything in Startup.Auth.cs you just need to setup dependency injection correctly.

Does new ASP.NET MVC identity framework work without Entity Framework and SQL Server?

I am new to ASP.NET MVC 5 and so I am trying to use it as much as possible to learn it by practice.
So I am thinking of using the new OWIN implementation of ASP.NET MVC to implement the authentication and authorization of my project. That said, I am building the project in a way that it can work with various types of databases.
So far I have used generic ADO.NET elements (e.g. DbDataReader etc) and I have refused to use any ORM. So I am wondering if I can go ahead with using the new identity system of ASP.NET or will I be bound to Entity Framework and SQL Server if I do so?
Not that simple. Not that hard either.
You'll have to write your custom implementation of:
IUserStore<TUser>
IUserPasswordStore<TUser>
IUserTwoFactorStore<TUser>
IUserClaimStore<TUser>
IRoleStore<TRole>
IUserSecurityStampStore<TUser, string>
IUserRoleStore<TUser, string>
UserManager<TUser>
Then create your own user implementation, from IUser<TKey>, like:
public class MyUser : IUser<string>
{
public string Id { get; set; }
public string UserName { get; set; }
}
Finally, from NuGet, remove AspNet.Identity.EntityFramework, which will remove EntityFramework too if you're not using it elsewhere.
Wherever your code breaks, rewrite it to use your custom implementations.
Tip
Create a MyUserRepository which implements items from 1 to 7.
Then, create a MyUserManager which implements item 8.
It will be damn easy to wire that up in place of default AspNet.Identity.EntityFramework classes.
To piggy-back on what ALMMa said, when I was working on my own custom implementation, I found this article to be invaluable:
Overview of Custom Storage Providers for ASP.NET Identity
It details no only what Interfaces need to be implemented, but goes into detail on how to implement them and gives code sample references to an actual MySQL implementation.
You just need to override some classes in the following manner to get basic role based authentication working without Entity Framework and SQL..
public partial class Startup
{
public static OAuthAuthorizationServerOptions OAuthOptions { get; private set; }
public void ConfigureAuth(IAppBuilder app)
{
// Configure the db context, user manager and role manager to use a single instance per request
app.CreatePerOwinContext(ApplicationDbContext.Create);
app.CreatePerOwinContext<ApplicationUserManager>(ApplicationUserManager.Create);
// Enable the application to use a cookie to store information for the signed in user
// and to use a cookie to temporarily store information about a user logging in with a third party login provider
app.UseCookieAuthentication(new CookieAuthenticationOptions());
app.UseExternalSignInCookie(DefaultAuthenticationTypes.ExternalCookie);
// Configure the application for OAuth based flow
OAuthOptions = new OAuthAuthorizationServerOptions
{
TokenEndpointPath = new PathString("/Token"),
Provider = new ApplicationOAuthProvider(),
AuthorizeEndpointPath = new PathString("/api/Account/ExternalLogin"),
AccessTokenExpireTimeSpan = TimeSpan.FromDays(14),
// In production mode set AllowInsecureHttp = false
AllowInsecureHttp = true
};
// Enable the application to use bearer tokens to authenticate users
app.UseOAuthBearerTokens(OAuthOptions);
}
}
public class ApplicationUser : IUser
{
public ApplicationUser()
{
Id = Guid.NewGuid().ToString();
Roles = new List<string>();
}
public virtual string Email { get; set; }
public List<string> Roles { get; set; }
public virtual string Password { get; set; }
public DateTime CreatedTime { get; set; }
public DateTime UpdatedTime { get; set; }
public string Id { get; }
public string UserName { get; set; }
public virtual void AddRole(string role)
{
Roles.Add(role);
}
public virtual void RemoveRole(string role)
{
Roles.Remove(role);
}
}
public class ApplicationUserManager : UserManager<ApplicationUser>
{
public ApplicationUserManager(IUserStore<ApplicationUser> store)
: base(store)
{
}
public static ApplicationUserManager Create(IdentityFactoryOptions<ApplicationUserManager> options,
IOwinContext context)
{
var manager =
new ApplicationUserManager(
new UserStoreService<ApplicationUser>(context.Get<ApplicationDbContext>().Users));
manager.PasswordHasher = new FusionPasswordHasher();
// Configure validation logic for passwords
manager.PasswordValidator = new PasswordValidator
{
RequiredLength = 6,
RequireNonLetterOrDigit = false,
RequireDigit = false,
RequireLowercase = false,
RequireUppercase = false
};
// Configure user lockout defaults
manager.UserLockoutEnabledByDefault = true;
manager.DefaultAccountLockoutTimeSpan = TimeSpan.FromMinutes(5);
manager.MaxFailedAccessAttemptsBeforeLockout = 5;
var dataProtectionProvider = options.DataProtectionProvider;
if (dataProtectionProvider != null)
manager.UserTokenProvider =
new DataProtectorTokenProvider<ApplicationUser>(dataProtectionProvider.Create("ASP.NET Identity"));
return manager;
}
public virtual async Task<IdentityResult> AddUserToRolesAsync(string userId, IList<string> roles)
{
var userRoleStore = (IUserRoleStore<ApplicationUser, string>) Store;
var user = await FindByIdAsync(userId).ConfigureAwait(false);
if (user == null)
throw new InvalidOperationException("Invalid user Id");
var userRoles = await userRoleStore.GetRolesAsync(user).ConfigureAwait(false);
// Add user to each role using UserRoleStore
foreach (var role in roles.Where(role => !userRoles.Contains(role)))
await userRoleStore.AddToRoleAsync(user, role).ConfigureAwait(false);
// Call update once when all roles are added
return await UpdateAsync(user).ConfigureAwait(false);
}
public virtual async Task<IdentityResult> RemoveUserFromRolesAsync(string userId, IList<string> roles)
{
var userRoleStore = (IUserRoleStore<ApplicationUser, string>) Store;
var user = await FindByIdAsync(userId).ConfigureAwait(false);
if (user == null)
throw new InvalidOperationException("Invalid user Id");
var userRoles = await userRoleStore.GetRolesAsync(user).ConfigureAwait(false);
// Remove user to each role using UserRoleStore
foreach (var role in roles.Where(userRoles.Contains))
await userRoleStore.RemoveFromRoleAsync(user, role).ConfigureAwait(false);
// Call update once when all roles are removed
return await UpdateAsync(user).ConfigureAwait(false);
}
}
If you want to read all the users in one short and store in the memory than you use the below style. And I strongly recommend you to read user only at the time of login for the you need to add your logic in "UserStoreService" class.
public class ApplicationDbContext : IDisposable
{
private ApplicationDbContext(IList<ApplicationUser> users)
{
Users = users;
}
public IList<ApplicationUser> Users { get; set; }
public void Dispose()
{
}
public static ApplicationDbContext Create()
{
//You can use any database and hook it here
var users = new List<ApplicationUser>
{
new ApplicationUser
{
UserName = "a#a.com",
Email = "a#a.com",
Password = "test",
Roles = new List<string> {"Admin", "Admin2"}
},
new ApplicationUser
{
UserName = "a#a2.com",
Email = "a#a2.com",
Password = "test2",
Roles = new List<string> {"Admin"}
}
};
return new ApplicationDbContext(users);
}
}
public class ApplicationOAuthProvider : OAuthAuthorizationServerProvider
{
public override async Task GrantResourceOwnerCredentials(OAuthGrantResourceOwnerCredentialsContext context)
{
var userManager = context.OwinContext.GetUserManager<ApplicationUserManager>();
var user = await userManager.FindAsync(context.UserName.ToLower(), context.Password);
if (user == null)
{
context.SetError("invalid_grant", "The user name or password is incorrect.");
return;
}
try
{
var oAuthIdentity = await userManager.CreateIdentityAsync(user, context.Options.AuthenticationType);
var cookiesIdentity = await userManager.CreateIdentityAsync(user,
CookieAuthenticationDefaults.AuthenticationType);
var props = new AuthenticationProperties(new Dictionary<string, string>
{
{
"client_id", context.ClientId == null ? string.Empty : context.ClientId
},
{
"userName", context.UserName
}
});
var ticket = new AuthenticationTicket(oAuthIdentity, props);
context.Validated(ticket);
context.Request.Context.Authentication.SignIn(cookiesIdentity);
}
catch (Exception ex)
{
Trace.TraceError("FUSION Error ::: " + ex.Message + ex.InnerException);
Trace.TraceError(ex.Message);
}
}
public override Task TokenEndpoint(OAuthTokenEndpointContext context)
{
foreach (var property in context.Properties.Dictionary)
if (property.Value != null)
context.AdditionalResponseParameters.Add(property.Key, property.Value);
return Task.FromResult<object>(null);
}
public override Task ValidateClientAuthentication(OAuthValidateClientAuthenticationContext context)
{
// Resource owner password credentials does not provide a client ID.
if (context.ClientId == null)
context.Validated();
return Task.FromResult<object>(null);
}
}
public class AppPasswordHasher : IPasswordHasher
{
public string HashPassword(string password)
{
return password;
}
public PasswordVerificationResult VerifyHashedPassword
(string hashedPassword, string providedPassword)
{
if (hashedPassword == HashPassword(providedPassword))
return PasswordVerificationResult.Success;
return PasswordVerificationResult.Failed;
}
}
Method like "FindByNameAsync" ; where you need to read user from db on demand/login
public class UserStoreService<TUser> : IUserStore<TUser>,
IUserPasswordStore<TUser>,
IUserRoleStore<TUser>
where TUser : ApplicationUser
{
private readonly IList<TUser> _users;
public UserStoreService(IList<TUser> users)
{
_users = users;
}
public virtual Task SetPasswordHashAsync(TUser user, string passwordHash)
{
user.Password = passwordHash;
return Task.FromResult(0);
}
public virtual Task<string> GetPasswordHashAsync(TUser user)
{
return Task.FromResult(user.Password);
}
public virtual Task<bool> HasPasswordAsync(TUser user)
{
return Task.FromResult(user.Password != null);
}
public virtual Task AddToRoleAsync(TUser user, string roleName)
{
user.AddRole(roleName);
return Task.FromResult(0);
}
public virtual Task RemoveFromRoleAsync(TUser user, string roleName)
{
user.RemoveRole(roleName);
return Task.FromResult(0);
}
public virtual Task<IList<string>> GetRolesAsync(TUser user)
{
return Task.FromResult((IList<string>) user.Roles);
}
public virtual Task<bool> IsInRoleAsync(TUser user, string roleName)
{
return Task.FromResult(user.Roles.Contains(roleName));
}
public virtual void Dispose()
{
}
public virtual Task CreateAsync(TUser user)
{
user.CreatedTime = DateTime.Now;
user.UpdatedTime = DateTime.Now;
_users.Add(user);
return Task.FromResult(true);
}
public virtual Task UpdateAsync(TUser user)
{
// todo should add an optimistic concurrency check
user.UpdatedTime = DateTime.Now;
_users.Remove(user);
_users.Add(user);
return Task.FromResult(true);
}
public virtual Task DeleteAsync(TUser user)
{
return Task.FromResult(_users.Remove(user));
}
public virtual Task<TUser> FindByIdAsync(string userId)
{
return Task.FromResult(_users.FirstOrDefault(u => u.Id == userId));
}
public virtual Task<TUser> FindByNameAsync(string userName)
{
// todo exception on duplicates? or better to enforce unique index to ensure this
return Task.FromResult(_users.FirstOrDefault(u => u.Email == userName));
}
}
[Authorize(Roles = "Admin")]
public class RolesController : ApiController
{
public IEnumerable<string> Get()
{
return new[] {"value3", "value4"};
}
}
Source Code (github)
It is bound to Entity Framework and SQL Server by default, but you can easily plug in other data stores such as SharePoint, Windows Azure Storage Table Service, NoSQL databases, etc., and you get to retain control of the database schema.
Further Reading
Introduction to ASP.NET Identity

Storing search parameters

I'm currently building a website in .NET MVC 4, using Entity Framework to access SQL Server.
The website should have a complex search with multiple choices for the user, create a new search (free search), choose from the last 5 searches (history search), choose from stored search parameters.
What I'm having trouble with is the concept of saving the search parameters/sql string, because it's not sessional/cache based and should be stored somewhere (SQL Server / MongoDB / XML) I'm having the hard time in taking the most optimized path, if it's the SQL way then maybe create an entity that stores the search parameters as entities and afterward converting it into a SQL string for the search, or store it in XML and than serialize it with JSON.
Some fields of the search are not an exact db/entity match and requires summing/converting (like hours that would be calculated into certain time).
I'm more inclined to take out the best of Entity Framework abilities for the cause.
Would like to hear some expert thoughts if possible, Thank you.
Not sure if this is the "most optimized" path, but thought it seemed simple to implement:
//POCO class of item you want to search from database
public class SearchableItem
{
public string Name { get; set; }
public int Age { get; set; }
}
//MVC View Model for search page
public class SearchParamaters
{
public int? MinAge { get; set; }
public int? MaxAge { get; set; }
}
//Storable version for database
public class SavedSearchParameters : SearchParamters
{
public int SavedSearchParametersId { get; set; }
}
//Use SearchParameters from MVC, or SavedSearchParamaters from EF
public IQueryable<SearchableItem> DoSearch(SearchParamaters sp)
{
IQueryable<SearchableItem> query = db.SearchableItems;
if (sp.MinAge.HasValue) query = query.Where(x => x.Age >= sp.MinAge.Value);
if (sp.MaxAge.HasValue) query = query.Where(x => x.Age <= sp.MaxAge.Value);
return query;
}
You could also serialize the SearchParameters class as XML/JSON and save it wherever, then deserialize it and pass it to the DoSearch method as normal, then you wouldn't have to change the DB schema every time you wanted to add search parameters
EDIT: Full example using serialization
\Domain\Person.cs
namespace YourApp.Domain
{
public class Person
{
public string Name { get; set; }
public int Age { get; set; }
}
}
\Domain\SavedPersonSearch.cs
namespace YourApp.Domain
{
//Entity object with serialized PersonSearchParameters
public class SavedPersonSearch
{
public int Id { get; set; }
public string Name { get; set; }
public string Parameters { get; set; }
}
}
\Models\PersonSearchParameters.cs
namespace YourApp.Models
{
//MVC View Model for search page
public class PersonSearchParameters
{
public int? MinAge { get; set; }
public int? MaxAge { get; set; }
}
}
\Helpers\SearchProvider.cs
using YourApp.Domain;
using YourApp.Models;
namespace YourApp.Helpers
{
public class SearchProvider
{
private YourAppDbContext _context;
public SearchProvider(YourAppDbContext context)
{
//This example uses the DbContext directly
//but you could use a Unit of Work, repository, or whatever
//design pattern you've decided on
_context = context;
}
public IQueryable<Person> SearchPersons(int savedPersonSearchId)
{
var savedSearch = _context.SavedPersonSearches.Find(savedPersonSearchId);
//deserialize (example assumes Newtonsoft.Json)
var searchParams = JsonConvert.Deserialize<PersonSearchParameters>(savedSearch.Parameters);
return SearchPersons(searchParams);
}
public IQueryable<Person> SearchPersons(PersonSearchParameters sp)
{
IQueryable<Person> query = _context.Persons;
if (sp.MinAge.HasValue) query = query.Where(x => x.Age >= sp.MinAge.Value);
if (sp.MaxAge.HasValue) query = query.Where(x => x.Age <= sp.MaxAge.Value);
return query;
}
public void SavePersonSearch(PersonSearchParameters sp, string name)
{
var savedSearch = new SavedPersonSearch { Name = name };
savedSearch.Parameters = JsonConvert.Serialize(sp);
_context.SavedPersonSearches.Add(savedSearch);
_context.SaveChanges();
}
}
}
\Controllers\PersonController.cs
namespace YourApp.Controllers
{
public class PersonsController : Controller
{
private SearchProvider _provider;
private YourAppDbContext _context;
public PersonsController()
{
_context = new YourAppDbContext();
_provider = new SearchProvider(_context);
}
//Manual search using form
// GET: /Persons/Search?minAge=25&maxAge=30
public ActionResult Search(PersonSearchParameters sp)
{
var results = _provider.SearchPersons(sp);
return View("SearchResults", results);
}
//Saved search
// GET: /Persons/SavedSearch/1
public ActionResult SavedSearch(int id)
{
var results = _provider.SearchPersons(id);
return View("SearchResults", results);
}
[HttpPost]
public ActionResult SaveMySearch(PersonSearchParameters sp, name)
{
_provider.SavePersonSearch(sp, name);
//Show success
return View();
}
}
}
conver your parameters to Base64 string. It would help you to create any hard queries for example, http://www.jobs24.co.uk/SearchResults.aspx?query=djAuMXxQUzoxMHx2MC4x&params=cXVlcnlmaWx0ZXI6 decode base64 use this service http://www.opinionatedgeek.com/DotNet/Tools/Base64Decode/default.aspx
you can also take a look on http://aws.amazon.com/cloudsearch/ it may be give you an idea about worh with parameters in your project
Something like this could work:
Store the search parameters in json/xml and persist in DB table.
1. When you want to edit the search parameters (if you even allow this), use the json/xml to pre-fill the selected parameters so user can edit criteria.
2. When user wants to run search, take parameters from json and create/run the query.
OR
Store the search parameters in json/xml and persist in DB table and also create the sql query and store the sql string (after validating parameters)
1. When you want to edit the search parameters (if you even allow this), use the json/xml to pre-fill the selected parameters so user can edit criteria.
2. When user wants to run search, simply take the saved query string and execute it.

Resources