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.
Related
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.
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
I am new to .NET and Azure, and am trying to create a simple Web API, to help me learn. I have two collections of DocumentDB documents. The documents in each collection are defined as follows:
public class Log
{
[JsonProperty(PropertyName = "id")]
public string Id { get; set; }
[JsonProperty(PropertyName = "studentName")]
public string StudentName { get; set; }
[JsonProperty(PropertyName = "assignment")]
public string Assignment { get; set; }
[JsonProperty(PropertyName = "dueDate")]
public DateTime DueDate { get; set; }
[JsonProperty(PropertyName = "goal")]
public string Goal { get; set; }
[JsonProperty(PropertyName = "reflection")]
public string Reflection { get; set; }
[JsonProperty(PropertyName = "sessions")]
public List<Session> Sessions { get; set; }
}
public class Session
{
[JsonProperty(PropertyName = "id")]
public string Id { get; set; }
[JsonProperty(PropertyName = "date")]
public DateTime Date { get; set; }
[JsonProperty(PropertyName = "duration")]
public TimeSpan Duration { get; set; }
[JsonProperty(PropertyName = "comment")]
public string Comment { get; set; }
}
Here is an example of what I have for the Log documents:
{
"id": "2",
"studentName": "Joe student",
"assignment": "Web APIs",
"dueDate": "0001-01-01T00:00:00",
"goal": "Keep mistakes to a minimum.",
"reflection": "I'm getting there.",
"sessions": [ ]
}
Here is an example of what I have for the Session documents:
{
"id": "7",
"date": "2015-04-26T00:00:00",
"duration": "00:30:00",
"comment": "Practiced for 30 minutes"
}
I would like to relate Sessions with Logs such that each Log may have several sessions, but each session will be related to a single Log, and also a single Student. I think I want the sessions as separate documents, because I may want to get a list of Sessions by either Student, or Log. There would be Student documents as well.
I am struggling to add Sessions to a Log, without simply duplicating the session within the log. I would Like to get something like this, which lists the id's of related sessions:
{
"id": "2",
"studentName": "Joe student",
"assignment": "Web APIs",
"dueDate": "0001-01-01T00:00:00",
"goal": "Keep mistakes to a minimum.",
"reflection": "I'm getting there.",
"sessions": [1, 2, 7, 19, 22]
}
I would then like to display the log, by replacing the ids with the content of the actual documents.
Here are some related working code snippets ( Not necessarily correct) to show you my design structure:
public class PracticeLogRepository : DocumentDB
{
// ********** Logs section ********** //
private PracticeSessionRepository _sessionsRepository;
// specifies the database and document collection used by the repository
public PracticeLogRepository() : base("Project3", "Logs") { }
// Gets a list of practice Logs
public Task<List<Practice.Log>> GetLogsAsync()
{
return Task<List<Practice.Log>>.Run(() => Client
.CreateDocumentQuery<Practice.Log>(Collection.DocumentsLink)
.ToList());
}
// Gets the practice Log with the matching id
public Task<Practice.Log> GetLogAsync(string id)
{
return Task<Practice.Log>.Run(() => Client
.CreateDocumentQuery<Practice.Log>(Collection.DocumentsLink)
.Where(pl => pl.Id == id)
.AsEnumerable()
.FirstOrDefault());
}
...
public class PracticeSessionRepository : DocumentDB
{
// ********** Session section ********** //
// specifies the database and document collection used by the repository
public PracticeSessionRepository() : base("Project3", "Sessions") { }
// Gets a list of practice Sessions
public Task<List<Practice.Session>> GetSessionsAsync()
{
return Task<List<Practice.Session>>.Run(() => Client
.CreateDocumentQuery<Practice.Session>(Collection.DocumentsLink)
.ToList());
}
// Gets the practice Log with the matching id
public Task<Practice.Session> GetSessionAsync(string id)
{
return Task<Practice.Session>.Run(() => Client
.CreateDocumentQuery<Practice.Session>(Collection.DocumentsLink)
.Where(pl => pl.Id == id)
.AsEnumerable()
.FirstOrDefault());
}
...
public class LogController : ApiController
{
private PracticeLogRepository _logsRepository;
public LogController()
{
_logsRepository = new PracticeLogRepository();
}
// GET: api/Log
public async Task<IHttpActionResult> Get()
{
var logs = await _logsRepository.GetLogsAsync();
if (logs != null)
return Ok(logs);
return NotFound();
}
// Get: api/Log/{id}
public async Task<IHttpActionResult> Get(string id)
{
var log = await _logsRepository.GetLogAsync(id);
if (log != null)
return Ok(log);
return NotFound();
}
...
public class SessionController : ApiController
{
private PracticeSessionRepository _sessionsRepository;
public SessionController()
{
_sessionsRepository = new PracticeSessionRepository();
//_logsRepository = new PracticeLogRepository();
}
// GET: api/Session
public async Task<IHttpActionResult> Get()
{
var sessions = await _sessionsRepository.GetSessionsAsync();
if (sessions != null)
return Ok(sessions);
return NotFound();
}
// Get: api/Session/{id}
public async Task<IHttpActionResult> Get(string id)
{
var session = await _sessionsRepository.GetSessionAsync(id);
if (session != null)
return Ok(session);
return NotFound();
}
...
Since I am teaching myself, and am extremely new .NET and C#, and of course DocumentDB, I am struggling to code this. I would really appreciate a simple example of how I should create the Log document, and also how to update it by adding sessions.
If you need to see more of my code, just ask. I just don't want to over complicate this.
I'd like to make sure I'm understanding the question, here's a recap of my understanding:
You have a 1-to-many relationship between Log and Session.
You have a 1-to-1 relationship between Log and Student.
You'd like to query Sessions by either Student or Log.
You'd like to populate data from these relationships (e.g. get the session data)
Please keep in mind DocumentDB is a NoSQL database and does not support inter-document JOINs.
I'd recommend revisiting how you approach modeling your data - in particular how you represent relationships (e.g. whether to keep Sessions and Logs as separate documents).
The following article talks about modeling data in detail:
http://azure.microsoft.com/en-us/documentation/articles/documentdb-modeling-data/
To summarize the key points of the article - I would pick the appropriate trade-off between normalizing (e.g. keeping Sessions and Logs as separate documents) and de-normalizing (e.g. keeping Sessions and Logs in the same document) given your application's use case. In general, I prefer de-normalizing when you have a read-heavy application and normalizing when you have a write-heavy application.
If you choose to normalize - you can simply make a follow-up request to get the session data for a log. Extra credit: You can even take this a step further and remove the need of having to make multiple network requests by writing a stored procedure.
If you choose to de-normalize, you can simply query for the entire document and everything will be populated automatically. The drawback is you may have to fan-out writes to multiple documents if you update session data.
I'm working on a website, where I need to retrieve pricelists, from another database on the same SQL Server as my Umbraco database.
It's a requirement that it has to be in a separate database.
I have made a new connection string Pricelist and used EF database-first.
PriceList repository:
namespace UmbracoCMS.Repository{
using System;
using System.Collections.Generic;
public partial class Prisliste
{
public string Kode { get; set; }
public string Speciale { get; set; }
public string Ydelsesgruppe { get; set; }
public string Gruppe { get; set; }
public string Ydelse { get; set; }
public string Ydelsestekst { get; set; }
public string Anaestesi { get; set; }
public string Indlæggelse { get; set; }
public Nullable<double> Listepris { get; set; }
public Nullable<int> WebSort { get; set; }
public string YdelsesTekstDK { get; set; }
public string Frapris { get; set; }
public Nullable<int> Sortering { get; set; }
}
}
PriceListController class:
using System;
using System.Linq;
using System.Web.Mvc;
using UmbracoCMS.Repository;
namespace UmbracoCMS.Controllers{
public class PriceListController : Umbraco.Web.Mvc.SurfaceController {
[HttpGet]
public PartialViewResult GetPriceList(string contentTitle){
var db = new PricelistContext();
var query = from b in db.Prislistes orderby b.Speciale select b;
Console.WriteLine("records in the database:");
foreach (var item in query)
{
Console.WriteLine(item.Speciale);
}
return PartialView("~/views/partials/PriceList.cshtml");
}
}
}
What I want is to load the prices for a treatment, based on a property on the document type. I'm just not sure how do this in umbraco since I'm fairly new a umbraco.
So when a treatment page is requested, I need to take the property ContentTitle value. Use it to retrieve all records with the same Speciale and display them in a list/table.
With a query
.where(b.Speciale = contentTitle)
It would be great if someone could help a little, or lead me in the right direction.
Also is it possible to do it in the same http request? Or should I use partial view or macros to both get the properties of the document type, from the umbraco database, and the records from the pricelist database at the same time when a user go to the treatment page?
Or is there a better way to do this?
Update:
Thanks a lot, for the great answer Ryios.
I got a question more.
using System;
using System.Linq;
using System.Web.Mvc;
namespace UmbracoCMS.Controllers
{
public class PriceListSurfaceController : Umbraco.Web.Mvc.SurfaceController
{
public ActionResult GetPriceList(string contentTitle)
{
PricelistContext.RunInContext(db =>
{
var result = db.Prislistes.OrderBy(p => p.Speciale);
});
return View(result);
}
}
}
I got it working, so it call the method and the data from the Pricelist Database is shown in:
var result = db.Prislistes.OrderBy(p => p.Speciale);
Now I just need to get the list of prices out to the view again, so I can show a list or table of the prices.
Do you have a suggestion on how I can this in Umbraco. Normally I would return a ViewModel in MVC like:
return View(new ListViewModel(result));
and use it in the view like:
#model Project.ViewModels.ListViewModel
So I can loop through it.
But I want to still have the properties from the the "Home"/"TreatmentPage" Document type.
Should I do it with a partialView or is there a better way?
Solved
I thought I wanted to share it, if anyone else is in a similar situaction.
Controller:
namespace UmbracoCMS.Controllers
{
public class PriceListSurfaceController : Umbraco.Web.Mvc.SurfaceController
{
public PartialViewResult PriceList(string contentTitle)
{
List<Prisliste> result = null;
PricelistContext.RunInContext(db =>
{
result = db.Prislistes.Where(p => p.Speciale == contentTitle)
.OrderBy(p => p.Speciale).ToList();
});
var model = result.Select( pl => new PrislistVm()
{
Speciale = pl.Speciale,
Listepris= pl.Listepris
});
return PartialView(model);
}
}
}
ViewModel:
namespace UmbracoCMS.ViewModels
{
public class PrislistVm
{
public PrislistVm()
{
Results = new List<Prisliste>();
}
public List<Prisliste> Results { get; set; }
public string Speciale { get; set; }
public double listepris { get; set; }
}
}
View/PriceListSurface:
#model IEnumerable<UmbracoCMS.ViewModels.PrislistVm>
#{
ViewBag.Title = "PriceList";
}
<h2>PriceList</h2>
#foreach (var item in Model)
{
#item.Speciale
#item.Listepris
}
Your going to have a memory leak if you load your EF context like that. I recommend creating a method to wrap it for you with a llambda callback. Put it in your context class.
public static void RunInContext(Action<PricelistContext> contextCallBack)
{
PricelistContext dbContext = null;
try
{
dbContext = new PricelistContext();
contextCallBack(dbContext);
}
finally
{
dbContext.Dispose();
dbContext = null;
}
}
//Example Call
PricelistContext.RunInContext(db => {
var result = db.PrisListes.OrderBy(p => p.Speciale);
//loop through your items
});
To get the Value of the DocumentType, it depends on the calling context. Assuming you are using a Razor Template that is attached to the document type, that is associated with a Content Page.
#inherits Umbraco.Web.Mvc.UmbracoTemplatePage
#{
Layout = "ContentPageLayout.cshtml";
}
#* Call GetPriceList on PriceListController with Parameter contentTitle *#
#Html.Action("GetPriceList", "PriceListSurface", new { contentTitle = Model.Content.GetPropertyValue<string>("ContentTitle") });
In the above example, I have created a document type with a property called ContentTitle that is associated with a view called ContentPage. Then I created content in the backoffice Content section called "Home" that uses the document type. Giving me a url like
http://localhost/home
Also, your SurfaceController will not work. Umbraco's logic for mapping the routes for surface controllers has some requirements for your surface controller's naming conventions. You have to end the name of the class with "SurfaceController" and then it get's called PriceListSurfaceController, then it maps the controller with a name of "PriceListSurface".
Here's the documentation for the SurfaceController features.
http://our.umbraco.org/documentation/Reference/Mvc/surface-controllers
Using a surface controller is the right logic. It's not good practice to have your Data Layer code calls in the UmbracoTemplatePage. 1, because RazorTemplates are interpreted/compiled and SurfaceController's are JIT compiled int the dll, so SurfaceController code is WAY faster. 2 Because you can make asynchronous Controller calls in MVC Razor. If it was all in the view it would make it really difficult to convert everything to be asynchronous. It's best to keep server side logic in a controller.
Optionally, you can Hijack an Umbraco route and replace it with a custom controller that doesn't have to inherit from SurfaceController, which makes it possibly to surface content to the browser that is or isn't part of umbraco.
http://our.umbraco.org/documentation/Reference/Mvc/custom-controllers
You can also create a new section in the backoffice to manage your Price List "the ui framework for building one is written against AngularJS"
http://www.enkelmedia.se/blogg/2013/11/22/creating-custom-sections-in-umbraco-7-part-1.aspx
I am attempting to seed data for an MVC 4 project using SQL server 4.0 as the database engine, using the Microsoft MVC music store tutorial as an example. I have set up a seed and DB context models, but the controller is not able to find the data. I have verified that the the database file is created in App_Data and verified that SetIntitializer is correctly set up in Application_Start. Here is what I have for code:
Seed data:
namespace RationalMethodApp.Models
{
public class StartData : CreateDatabaseIfNotExists<RationalMethodEntities>
{
protected override void Seed(RationalMethodEntities context)
{
new List<Basin>
{
new Basin {
basinId = 1, // attempting to force a key value, will remove
Name = "Replace me with a real basin",
Location = "In a real location",
drainageArea = 0.0M
}
}.ForEach(b => context.Basins.Add(b));
Controller:
public ActionResult Index(int? bsnId)
{
if (bsnId == null) // here to force a key value, will change
bsnId = 1;
var basin = rmDb.Basins.Find(bsnId);
return View(basin);
}
The context class is:
namespace RationalMethodApp.Models
{
public class RationalMethodEntities : DbContext
{
public DbSet<Basin> Basins { get; set; }
public DbSet<SubArea> SubAreas { get; set; }
public DbSet<IdfCurve> IdfCurves { get; set; }
public DbSet<Analysis> Analyses { get; set; }
public DbSet<FlowSegment> FlowSegments { get; set; }
public DbSet<SheetFlowN> SheetFlowNs { get; set; }
public DbSet<RunoffCoefficient> RunoffCoefficients { get; set; }
public DbSet<StormFrequency> stormFrequencies { get; set; }
}
}
The debugger tells me that the "basin" object is still null in the controller after the .Find. This must be a simple, basic thing that I have overlooked, but all of the help I can find on-line assumes that the askers know what they are doing - not true in my case! I have also checked the discussion at Entity Framework database seed doesn't seed
but this does not seem to be the answer. Please bear with a total noob question.
You don't show the full code of you seed, so i can't really be sure, but you might be missing the Context.Changes().
As well you wrote
public class StartData : CreateDatabaseIfNotExists<RationalMethodEntities>
If you don't delete your database before the application start, it won't do anything as the db already exists.
You could use :
public class StartData : DropCreateDatabaseAlways <RationalMethodEntities>
to drop it every time you start or
public class StartData : DropCreateDatabaseAlways <DropCreateDatabaseIfModelChanges >
to drop db when Model changes (which is great for start of dev)
To debug: Drop your database, kill your application server (so it goes back to application start), breakpoint in your seed. Start Debug, if it goes in seed, check that data is in it after SaveChange().