ASP MVC 5 EF - Saving application settings - - c#

I want to save my application settings like wordpress saves its app settings in wp_options table.
wp_options table schema is as follows:
option_id option_name option_value autoload
-------------------------------------------------
1 siteurl 'mywebsite.com' yes
2 blogname 'myblog' yes
If I save like this then I wont be able to directly access values like object['siteurl']. Do I need to make custom mappings?
I am using Entity Framework btw.

Here's a mapping sample just to give you an idea.
public class Option
{
public int Id { get; set; }
public string Name { get; set; }
public string Value { get; set; }
public bool IsAutoload { get; set; }
}
Create a dictionary..
Dictionary<string, Option> WP_Options = new Dictionary<string, Option>();
List<Option> options = context.Wp_Options.Select(r => new Option()
{
Id = r.option_id,
Name = r.option_name,
Value = r.option_value,
IsAutoload = r.option_autoload == "yes"
}; // store records into a list
foreach(Option option in options)
{
WP_Options.Add(option.Name, option); // Store to dictionary
}
You can now access your options like:
Option siteUrl = WP_Options["siteurl"];
var val = siteUrl.Value;
bool autoload = siteUrl.IsAutoload;
If you are familiar with singleton classes then I'd suggest creating one that exposes the dictionary WP_Options. With this, you can access the same instance of the WP_Options across your application.
You'd just have to handle the option saving to the database.
Here's a little sample:
foreach(KeyValuePair<string, Option> entry in WP_Options)
{
if(context.Wp_Options.FirstOrDefault(o => o.Name == entry.Value) != null)
{
// Entry exists do an update logic
}
else
{
// Entry does not exist do an insert logic
}
}
// save data context

Related

Best way to send multiple email types in ASP.NET MVC

Hi there to the good friends of SO!
This is more of a design question so I'll get into a detailed example.
Let me explain the way we're sending emails.
In various parts of the application, we create entries in our Notification table for different kinds of email we might have to send.
For eg: The NotificationQueue table looks like this:
NotificationQueueID OrderID EmailType Notes SentDatetime
1 461196 OrderUpdate SomeNote1 2020-09-01 14:45:13.153
2 461194 OrderCancellation SomeNote2 2020-09-01 14:45:13.153
It's accessed using the property in the DbContext as:
public DbSet<NotificationQueue> NotificationQueues { get; set; }
The different types of email is modeled in an enum:
public enum TypeOfEmail
{
OrderCancellation,
OrderUpdate
}
We have a EmailModel class that has a TicketsInNotificationQueue property that has a list of any of the email types we have. For eg: At any given time, it can have list of either UpdatedTickets or CancelledTickets. The email type says what type of tickets are in the TicketsInNotificationQueue property.
public class EmailModel
{
public EmailModel(TypeOfEmail emailType, TicketsInNotificationQueue ticketsInNotificationQueue)
{
EmailType = emailType;
TicketsInNotificationQueue = ticketsInNotificationQueue;
}
public TypeOfEmail EmailType { get; set; }
public TicketsInNotificationQueue TicketsInNotificationQueue { get; set; }
}
public class TicketsInNotificationQueue
{
public List<OrderCancellation> CancelledTickets { get; set; }
public List<OrderUpdate> UpdatedTickets { get; set; }
}
public class OrderCancellation : CommonOrderInformation
{
public string SomeOrderId { get; set; }
}
public class OrderUpdate: CommonOrderInformation
{
public string SomeUpdateRelatedProperty { get; set; }
}
public class CommonOrderInformation
{
public int NotificationQueueId { get; set; }
public string ReferenceNumber { get; set; }
}
There's a method that retrieves tickets from Notification table:
public async Task<TicketsInNotificationQueue> GetTicketsfromNotificationQueueAsync(TypeOfEmail emailType)
{
var ticketsInNotificationQueue = new TicketsInNotificationQueue();
using (var dbCon = GetSomeDbContext())
{
var notifications = dbCon.NotificationQueues.Where(x => x.EmailType == emailType.ToString()).ToList();
foreach (var ntf in notifications)
{
if (ntf.EmailType == TypeOfEmail.OrderCancellation.ToString())
{
if (ticketsInNotificationQueue.CancelledTickets == null)
{
ticketsInNotificationQueue.CancelledTickets = new List<OrderCancellation>();
}
ticketsInNotificationQueue.CancelledTickets.Add(new OrderCancellation()
{
NotificationQueueId = ntf.NotificationQueueID,
ReferenceNumber = ntf.OrderID,
SomeOrderId = "Something from a table."
});
}
else if (ntf.EmailType == TypeOfEmail.OrderUpdate.ToString())
{
if (ticketsInNotificationQueue.UpdatedTickets == null)
{
ticketsInNotificationQueue.UpdatedTickets = new List<OrderUpdate>();
}
var notes = dbCon.NotificationQueues.FirstOrDefault(x => x.NotificationQueueID == ntf.NotificationQueueID)?.Notes;
ticketsInNotificationQueue.UpdatedTickets.Add(new OrderUpdate()
{
NotificationQueueId = ntf.NotificationQueueID,
ReferenceNumber = ntf.OrderID,
SomeUpdateRelatedProperty = "Something from a table."
});
}
}
}
return ticketsInNotificationQueue;
}
Now I just take this list, and filter out the notificationIds for the type of tickets that I just received, and work on them down the line. (I need those notificationIds to set the SentDatetime after the notification has been sent).
var ticketsReceived = false;
notificationIds = new List<int>();
if (ticketsInNotificationQueue.CancelledTickets != null && ticketsInNotificationQueue.CancelledTickets.Any())
{
ticketsReceived = true;
notificationIds = ticketsInNotificationQueue.CancelledTickets.Select(x => x.NotificationQueueId).ToList();
}
else if (ticketsInNotificationQueue.UpdatedTickets != null && ticketsInNotificationQueue.UpdatedTickets.Any())
{
ticketsReceived = true;
notificationIds = ticketsInNotificationQueue.UpdatedTickets.Select(x => x.NotificationQueueId).ToList();
}
if (ticketsReceived)
{
// Proceed with the process of sending the email, and setting the `SentDateTime`
}
The problem I see here is that as the type of emails grows bigger, let's say 10-20, the method to retrieve tickets and filter them out later needs to grow so big that it's going to spin out of control in terms of readability and code manageability which I'm not liking at all. The part where I need to check what emailType is requested in the fetch and what emailType has been received(to get the corresponding notificationIds for SentDateTime update).
So is there some other way to design this workflow (I'm even open to using reflection and such) to make it more manageable and concise?
Any help would be greatly appreciated!
There is significant improvements that you can make to the existing system and the existing code. In the interest of having a more complete answer I'm going to recommend a not-too-expensive system overhaul and then proceed to your exact answer.
A different and industry standard approach
You already have the data structure correct, this is a perfect job for distributed persistent queues, where you don't need to worry about querying the database as much; instead you just enqueue the messages and have a processor that deals with them. Since you're using C# and .net, I strongly encourage you to check out Azure Service Bus. This is effectively a large queue where you can send messages (in your case send email requests) and you can enqueue your messages to different channels in the service bus depending on their type.
You could also look into creating a queue processor / which Azure Functions have a trigger out of the box. Once your email is sent, then you can write to your DB, we've sent this email.
So, the good design looks like
Have distributed persistent queues, channels / enqueue the email requests to them directly.
If you want to process them at a cadence, run your processor using cron - which most industry solutions support.
If you want to process them as they are ending up in the queue, use a trigger.
You can enrich your processor based on your scenario, it looks like it has something to do with orders, so you may need to handle cases like not sending an already queued email after an order in cancelled, etc..
Improving what you have
Due to some circumstances, the solution above might not be available to you - so let's get to it.
See how to refactor switch statements (since you have one with if / else ifs)
https://sourcemaking.com/refactoring/smells/switch-statements
Ways to eliminate switch in code
You could get this through polymorphism, just create a base mail type and override the behaviors in subclasses. This way you can associate the correct queue with the correct email type.
Example:
var results = await getSomeEmails(OrderMail);
// returns a separate processor inherited from the base one, implemented in different ways.
var processor = ProcessorFactory.Create(OrderMail);
await processor.Send(results);
Some more improvements
foreach (var ntf in notifications)
{
if (ntf.EmailType == TypeOfEmail.OrderCancellation.ToString())
You are checking the email type over and over again unnecessarily in this loop, you should look into moving those statements above the for and check through the passed-in parameter, since you already know the type you're querying for.
Thank you for the answer #Mavi Domates.
But this is what I ended up doing:
I modified the EmailModel's TicketsInNotificationQueue property so that instead of having different types of classes for different types of email, we just have one type of common class. This will avoid having us to put those checks for checking what kind of email was requested in the fetch logic and also to retrieve notification Ids down the line (to update SentDateTime after email is sent) as indicated in the original question.
public class EmailModel
{
public EmailModel(TypeOfEmail emailType, IEnumerable<CommonEmailModel> ticketsInNotificationQueue)
{
EmailType = emailType;
TicketsInNotificationQueue = ticketsInNotificationQueue;
}
public TypeOfEmail EmailType { get; set; }
public IEnumerable<CommonEmailModel> TicketsInNotificationQueue { get; set; }
}
public enum TypeOfEmail
{
OrderCancellation,
OrderUpdate
}
I added a new class called: CommonEmailModel and removed all those different email type classes (classes for OrderCancellation, OrderUpdate etc.).
public class CommonEmailModel
{
// Common to all email types. A lot of email types only need these first 4 properties
public string EmailType { get; set; }
public int NotificationQueueId { get; set; }
public string OrderId { get; set; }
public string Notes { get; set; }
// Cancellation related
public string SomeOrderId { get; set; }
// Update related
public string SomeUpdateRelatedProperty { get; set; }
public static async Task<IEnumerable<CommonEmailModel>> GetEmailBodyRecordsAsync(TypeOfEmail emailType)
{
var emailModels = new List<CommonEmailModel>();
var emailEntries = await EmailNotificationQueue.GetEmailEntriesAsync(emailType);
var relevantOrdIds = emailEntries.Select(x => x.OrderID).Distinct().ToList();
using (var dbCon = GetSomeDbContext())
{
orders = dbCon.Orders.Where(x => relevantOrdIds.Contains(x.OrdNumber)).ToList();
}
foreach (var record in emailEntries)
{
var emailModel = new CommonEmailModel
{
EmailType = emailType,
NotificationQueueId = record.NotificationQueueID,
OrderId = record.OrderID,
Notes = record.Notes,
SomeOrderId = orders?.FirstOrDefault(o => o.OrdNumber == record.OrderID)?.SomeOrderIdINeed,
SomeUpdateRelatedProperty = orders?.FirstOrDefault(o => o.OrdNumber == record.OrderID)?.UpdateRelatedPropertyINeed
};
emailModels.Add(emailModel);
}
return emailModels;
}
}
I just get the records the following way:
var emailRecords = await CommonEmailModel.GetEmailBodyRecordsAsync(emailType);
And simply pass this to EmailModel constructor as the ticketsInNotificationQueue parameter. No need to do all that extra check of figuring out if records of certain emailType was requested. The views for OrderCancellation and OrderUpdate will use the common properties and their respective relevant properties that are present in the CommonEmailModel class.
if (emailRecords.Any())
{
var emailModel = new EmailModel(emailType, emailRecords);
}
Now all I have to do is pass the notification Ids to a method that marks the SentDateTime column with the current timestamp by simply calling:
if (emailWasSent)
{
await UpdateNotificationSentTimeAsync(emailRecords.Select(t => t.NotificationQueueId));
}
In the future if we keep on adding new emailType (most probably they'll carry the information in those 4 first common properties in CommonEmailModel), we can simply add new properties to the CommonEmailModel to accommodate that and just create a new view. This way I can avoid code repetition and complexity in the fetch and also at the end while updating the SentDateTime.

_context.add not saving to database

I have a controller which needs to pull the data from the form and add to database. I've done this numerous times before, but for some reason on this specific section of code it will not save at all.
I've got it pulling the info into the console.writeline and checked that it's all the correct info. I have also tried adding dummy data, but nothing will save. I've also tried using just _console.Add();, _console.Add(RACIResUser);, _context.RACIResUser.Add(RACIResUser), but nothing is saving. As I said, I can view it all the correct info in the console, and other parts of the form on the page are saving to a different table within the database, it's just this section that wont save.
Code is below:
Post Controller:
[HttpPost]
public async Task<IActionResult> InstanceProcess(string InstanceId, [Bind(Prefix="SectionInfo")]IEnumerable<ProcessOutput> secinf)
{
var res = (from i in _context.SOPRACIRes
select new ProcessOutput { SOPTemplateID = i.soptoptempid, valuematch = i.valuematch, JobTitleId = i.JobTitleId, DepartmentId = i.DepartmentId }).ToList();
ViewBag.res = res;
foreach (var item in secinf)
{
foreach (var data in (ViewBag.res))
{
ApplicationDbContext.RACIResChosenUser RACIResUser = new ApplicationDbContext.RACIResChosenUser();
var sel = Request.Form[data.valuematch + "-RES"];
Console.WriteLine("Dropdown res:");
Console.WriteLine(sel);
int onevalue = Convert.ToInt32(sel);
var status = "Pending";
RACIResUser.RACIResID = data.valuematch;
RACIResUser.UserId = onevalue;
RACIResUser.Status = status;
RACIResUser.soptoptempid = data.SOPTemplateID;
RACIResUser.InstanceId = getid;
Console.WriteLine("data valuematch:");
Console.WriteLine(RACIResUser.RACIResID);
Console.WriteLine("data SOPTemplateID:");
Console.WriteLine(data.SOPTemplateID);
_context.RACIResUser.Add(RACIResUser);
_context.SaveChanges();
}
}
}
DB context:
public class RACIResChosenUser {
[Key]
public int RACIResChosenID { get; set; }
public string RACIResID { get; set; }
public string Status {get; set;}
public string StatusComplete { get; set; }
public int UserId { get; set; }
public string soptoptempid { get; set; }
public string InstanceId { get; set; }
}
public DbSet<RACIResChosenUser> RACIResUser { get; set; }
cshtml:
#foreach(var data in (ViewBag.res)){
var dropname = item.valuematch + "-RES";
<div class="col-12 col-md-6">
#Html.DropDownList(#dropname,ViewBag.selectlist as SelectList, new { #class = "form-control" })
</div>
}
There are a few things that it may be without further info.
This section:
ApplicationDbContext.RACIResChosenUser RACIResUser = new ApplicationDbContext.RACIResChosenUser();
It may be easier to keep your custom object lowercase in the scope it is in like raciResUser to just not have your instantiated object by the same case as your complex type of it. It mostly likely may still work but that is a concern.
In your context you should have DbSet< RACIResUser>. If not that is a problem. But you probably do as usually you cannot even see the dbcontext.RACIResUser if it was not there.
Your 'Migrations' need to be up to date. If you just added this POCO recently and you have it missing in a database or elsewhere you need to make a migration like:
dotnet ef migrations Add (yourMigrationName)
Else it has no idea about the changes.
Are you certain your connection string is pointing to a database you think you are going to? Generally in ASP.NET Core you wire that up in the Startup.cs and it points to your appSettings.json. Else if you make up the plain jane vanilla that inherits from DBContext you usually have a section like this:
protected override void OnConfiguring(DbContextOptionsBuilder optionsBuilder)
{
if (!optionsBuilder.IsConfigured)
{
\#warning To protect potentially sensitive information in your connection string, you should move it out of source code. See http://go.microsoft.com/fwlink/?LinkId=723263 for guidance on storing connection strings.
optionsBuilder.UseSqlServer(#"Server=.;Database=db;Trusted_Connection=True;");
}
}
Where these settings would be matching what you want unless you want to override this with another location.

Entity Framework 6.1 Updating a Subset of a Record

I have a view model that encapsulates only some of the database model properties. These properties contained by the view model are the only properties I want to update. I want the other properties to preserve their value.
During my research I found this answer which appears to be perfect for my needs however, despite my best efforts, I cannot get the code to work as expected.
Here is an isolated example of what I came up with:
static void Main() {
// Person with ID 1 already exists in database.
// 1. Update the Age and Name.
Person person = new Person();
person.Id = 1;
person.Age = 18;
person.Name = "Alex";
// 2. Do not update the NI. I want to preserve that value.
// person.NINumber = "123456";
Update(person);
}
static void Update(Person updatedPerson) {
var context = new PersonContext();
context.Persons.Attach(updatedPerson);
var entry = context.Entry(updatedPerson);
entry.Property(e => e.Name).IsModified = true;
entry.Property(e => e.Age).IsModified = true;
// Boom! Throws a validation exception saying that the
// NI field is required.
context.SaveChanges();
}
public class PersonContext : DbContext {
public DbSet<Person> Persons { get; set; }
}
public class Person {
public int Id { get; set; }
[Required]
public string Name { get; set; }
[Required]
public int Age { get; set; } // this is contrived so, yeah.
[Required]
public string NINumber { get; set; }
}
What am I doing wrong?
You based your work on the post https://stackoverflow.com/a/15339512/2015959, but in the other thread the fields that weren't changed (and as such weren't in the attached model) weren't mandatory, and that's why it worked. Since your fields are required, you'll get this validation error.
Your problem can be solved by the solution provided in question Entity Framework validation with partial updates
It is the validation that is causing it not to be saved. You can disable validation with context.Configuration.ValidateOnSaveEnabled = false; and it will work. To validate specific fields you can call var error = entry.Property(e => e.Name).GetValidationErrors();. So you certainly can make an 'UpdateNameAndAge' method that only only enforces business rules and flags those properties as modified. No double query required.
private static bool UpdateNameAndAge(int id, string name, int age)
{
bool success = false;
var context = new PersonContext();
context.Configuration.ValidateOnSaveEnabled = false;
var person = new Person() {Id = id, Name = name, Age = age};
context.Persons.Attach(person);
var entry = context.Entry(person);
// validate the two fields
var errorsName = entry.Property(e => e.Name).GetValidationErrors();
var errorsAge = entry.Property(e => e.Age).GetValidationErrors();
// save if validation was good
if (!errorsName.Any() && !errorsAge.Any())
{
entry.Property(e => e.Name).IsModified = true;
entry.Property(e => e.Age).IsModified = true;
if (context.SaveChanges() > 0)
{
success = true;
}
}
return success;
}
(Edited for clarity)
The context must have a complete copy of the object to enforce business rules. This can only happen if the attached object has all the necessary properties populated or the partial view is merged with a complete copy before updating.
I believe that what you want to do is conceptually impossible: doing updates like this will require either a preserved pre-change copy, or two queries to the database because the business layer needs a full copy of the object for validation.

C# WCF - ADO.NET Entity model - DbSet empty?

I created a new WCF project in visual studio based on a existing database.
I made two operations. One operation writes a record (createProfile) to the database and one retrieve data (GetProfiles). My project exists of 3 files: web.config, a edmx file and my svc class.
CreateProfile works fine, I checked in SQL and the record is created.
GetProfiles never gives a response. When I debug the context.UserProfileSet always counts 0 values.
Any suggestions on what is going wrong?
[DataContract]
public partial class UserProfile
{
public int Id { get; set; }
[DataMember]
public string UserName { get; set; }
}
public class MusicOwnerService : IMusicOwnerService
{
IEnumerable<UserProfile> GetProfiles()
{
using (MusicOwnerDatabaseEntities context = new MusicOwnerDatabaseEntities())
{
return context.UserProfileSet.AsEnumerable();
}
}
public void CreateProfile()
{
using (MusicOwnerDatabaseEntities context = new MusicOwnerDatabaseEntities())
{
context.UserProfileSet.Add(new UserProfile { UserName = "John" });
context.SaveChanges();
}
}
}
As far as I know, you cant pass an IEnumerable object over the wire with WCF (unless youve a duplex binding of some sort??).. so you would be best to convert to a list and return that list like below:
List<UserProfile> GetProfiles()
{
using (MusicOwnerDatabaseEntities context = new MusicOwnerDatabaseEntities())
{
return context.UserProfileSet.ToList();
}
}

Getting IdConvention in RavenDB

Lets say we have a User class
public class User
{
public User() {
Created = DateTime.Now;
Tags = new List<string>();
}
public string FirstName { get; set; }
public string LastName { get; set; }
public DateTime Created {get;set;}
public IEnumerable<string> Tags {get; private set;}
}
And one might want a user to have an id like [FirstName]/[LastName] so we register an IdConvention like this:
_documentStore
.Conventions
.RegisterIdConvention<User>(
(dbname, commands, user) => user.FirstName +"/" + user.LastName ));
Now lets say you created a new user with a new set of tags attached. You want to store it in RavenDB if the User does not exist. However, if the User does exist you don't want to overwrite the existing Object as you want to keep the initial Created date. Therefore you only update the Tags enumeration with the values of the newly created User.
You might do something like this:
public void AddOrUpdateUser(User newUser) {
using (var session = _documentStore.OpenSession())
{
var existingUser = session.Load<User>("myFirstname/myLastname")
if(user != null) {
existingUser.Tags = user.Tags;
}
else {
session.Store(newUser);
}
session.SaveChanges();
}
}
However, if for some reason I changed my IdConvention, I have had to update the code above as well. Is there a way to reference the IdConvention registered in order to calculate the id for the newUser Object. With this id value you could check wether an item exists or not rather than creating the Id by yourself.
After registering an id convention, the GenerateDocumentKey method will use that convention instead of the default hilo generation scheme.
It needs some parameters, which are easiest to get at if you first cast the IDocumentSession to a real DocumentSession.
var s = ((DocumentSession) session);
var key = s.Conventions.GenerateDocumentKey(s.DatabaseName,
s.DatabaseCommands,
yourEntity);

Categories