diff --git a/.gitignore b/.gitignore new file mode 100644 index 0000000..e8093b5 --- /dev/null +++ b/.gitignore @@ -0,0 +1,182 @@ +## Ignore Visual Studio temporary files, build results, and +## files generated by popular Visual Studio add-ons. + + +# User-specific files +*.suo +*.user +*.sln.docstates + + +# Build results + + +[Dd]ebug/ +[Rr]elease/ +x64/ +build/ +[Bb]in/ +[Oo]bj/ + + +# Enable "build/" folder in the NuGet Packages folder since NuGet packages use it for MSBuild targets +!packages/*/build/ + + +# MSTest test Results +[Tt]est[Rr]esult*/ +[Bb]uild[Ll]og.* + + +*_i.c +*_p.c +*.ilk +*.meta +*.obj +*.pch +*.pdb +*.pgc +*.pgd +*.rsp +*.sbr +*.tlb +*.tli +*.tlh +*.tmp +*.tmp_proj +*.log +*.vspscc +*.vssscc +.builds +*.pidb +*.log +*.scc + + +# Visual C++ cache files +ipch/ +*.aps +*.ncb +*.opensdf +*.sdf +*.cachefile + + +# Visual Studio profiler +*.psess +*.vsp +*.vspx + + +# Guidance Automation Toolkit +*.gpState + + +# ReSharper is a .NET coding add-in +_ReSharper*/ +*.[Rr]e[Ss]harper + + +# TeamCity is a build add-in +_TeamCity* + + +# DotCover is a Code Coverage Tool +*.dotCover + + +# NCrunch +*.ncrunch* +.*crunch*.local.xml + + +# Installshield output folder +[Ee]xpress/ + + +# DocProject is a documentation generator add-in +DocProject/buildhelp/ +DocProject/Help/*.HxT +DocProject/Help/*.HxC +DocProject/Help/*.hhc +DocProject/Help/*.hhk +DocProject/Help/*.hhp +DocProject/Help/Html2 +DocProject/Help/html + + +# Click-Once directory +publish/ + + +# Publish Web Output +*.Publish.xml +*.pubxml + + +# NuGet Packages Directory +## TODO: If you have NuGet Package Restore enabled, uncomment the next line +#packages/ + + +# Windows Azure Build Output +csx +*.build.csdef + + +# Windows Store app package directory +AppPackages/ + + +# Others +sql/ +*.Cache +ClientBin/ +[Ss]tyle[Cc]op.* +~$* +*~ +*.dbmdl +*.[Pp]ublish.xml +*.pfx +*.publishsettings + + +# RIA/Silverlight projects +Generated_Code/ + + +# Backup & report files from converting an old project file to a newer +# Visual Studio version. Backup files are not needed, because we have git ;-) +_UpgradeReport_Files/ +Backup*/ +UpgradeLog*.XML +UpgradeLog*.htm + + +# SQL Server files +App_Data/*.mdf +App_Data/*.ldf +*.mdf +*.ldf + + +# ========================= +# Windows detritus +# ========================= + + +# Windows image file caches +Thumbs.db +ehthumbs.db + + +# Folder config file +Desktop.ini + + +# Recycle Bin used on file shares +$RECYCLE.BIN/ + + +# Mac crap +.DS_Store diff --git a/Changes.txt b/Changes.txt new file mode 100644 index 0000000..a7be433 --- /dev/null +++ b/Changes.txt @@ -0,0 +1,412 @@ +18.2.2014 - WPF StudentListView +* Added StudentListView with multiple selection option +* Fixed a couple of problems: a) IsFocused is not given by WPF automatically, so ViewModel had to be updated in such a manner that focused item is the last selected, +and b) Entity Framework has problems during saving changes when the entity marked to be deleted has children retrieved from the database and cached but not marked +to be deleted (though database is set to CASCADE DELETE, so it could actually solve this problem automatically) => one need to call Remove method instead of +setting State to Deleted since Remove sets State of all items to Deleted. + +17.2.2014 - WPF Desktop Core +* Implemented ConfirmationView and NotificationView + +3.1.2013 - Resources and Settings introduced + * Added a new folder Resources into StudentEvaluatorCore and two Resource files (Add New item - Resource File), one for neutral language, one for Czech + with one string to be displayed in StudentListViewModel.DeleteSelectedStudents + + NOTE: for editing multiple languages simultaneously, VS editor does not do great job. + Use http://www.zeta-resource-editor.com/index.html for better editing. + + * Added a Settings File into StudentEvaluatorConsoleApp and moved into its Properties (with Custom Tool Namespace set to Zcu.StudentEvaluator.Properties) + + NOTE: Settings allow configuring virtually anything from .config file with two scopes available. Application = any change that might have been done during the runtime + is not stored into .config file, when .Save method is called; User = any change can be stored but it is stored into user.config file automatically created for each + user running the application (e.g., "C:\Users\Josef\Local Settings\Data aplikac\Zcu\StudentEvaluatorConsoleAp_Url_th4hld2fq4lpjwp3o0jjr5m4n0lkjx2e\1.0.0.0\") + because App.config is considered to contain the defaults. + + NOTE2: The easiest way to create Settings is to Go to tab "Settings" in the project Properties. + +2.1.2013 - Fixed a couple of bugs in RelayCommand + +5.12.2013 - Contracts and Microsoft Code Digger +* Experiments with Microsoft Code Digger to show other possibilities to Debug the code + see http://research.microsoft.com/en-us/projects/codedigger/gettingstarted.aspx + + IMPORTANT STEPS TO REPRODUCE: + - Install Code Digger and modify its settings (Tools/Pex) to be able to process also other than Portable + classes Assemblies only, and also enable Diagnosis (which will create reports of processing) + - Go to StudentEvaluationContextExtensions.cs into DumpData method and launch from the context menu + "Generate Input/Output Table". It should give you some results. + - Unfortunately, Code Digger targets methods only, i.e., it cannot be run on Properties + - What is a more serious problem is that it fails to produce anything when the method + is a member of class that requires some interface to be passed into its constructor + because the underlying Pex machine does not know how to create the instance (this + can be found in the report file as "Pex needs help to find types") and currently, + there is no way to help Pex :-) Code Digger is Pre-Release technology. + Useful in some context (e.g., for automatic generation of UnitTests) but still + +* Added Code Contracts + IMPORTANT STEPS TO REPRODUCE: + - Install Code Contracts Tools since Code Contracts support in .NET is just an interface with + concrete implementation missing => this should add new tab into Properties of the project + - http://research.microsoft.com/en-us/projects/contracts/userdoc.pdf + - Enable "Static Contract Checking" (in the Properties) + - Check "Fail build on warning" + - Goto DialogService.cs in Register and uncomment Assumes + - Build the solution (and wait until CodeContracts finish - it may take minutes) + - Uncomment confirm.ConfirmAction in Main to see what happens (+ see contract class for IConfirmView) + - Choose one of three modes (see section 5.1 in userdoc) + - Add contracts that satisfies your needs + + + +5.12.2013 - Unit tests +* Added an unit test for StudentViewModel (with 100% code coverage) +* Fixed several bugs (and unwanted code) discovered by the testing + +5.12.2013 - Class Library +* General classes moved into a class library (StudentEvaluatorCore) being referenced in the Console Application +* Migration from EF 5.0 to 6.0 (enforced by the fact that EF 5.0 no longer available for the class library to use) + +25.11.2013 - MVVM Pattern Improvements +* MVVM Notes: + When it comes to MVVM pattern, one of the first problematic issues is how to display model dialogs to the user + while executing code in the view-model. Popping up a dialog, such as a MessageBox or a ChildWindow, from + the view-model is an anti-pattern that violates the separation of concerns that exists between the view + and the view-model. Currently, several approaches can be adopted, each of them having its pros and cons: + + 1) Violate MVVM concept and call the method to show dialog directly from ViewModel code (or from View). + One of the main benefits of MVVM is better application maintainability. If you separate concerns of presentation + (the view) from business logic (the view-model) and data (the model), making a change in one area is less likely + to impact other areas. Another benefit is testability. It is notoriously difficult to test the user interface + by simulating things like button clicks and mouse overs. Encapsulating functionality into a view-model means + that you can test it independently of the view, which is just another consumer of the view-model. If you were + to display a dialog from the view-model in order to get input from the user, it would be impossible to run + a unit test against the view-model because theres no way for a unit test to respond to the dialog. In addition, + MVVM promotes better workflow between designers and developers. Designers can wire up actions to elements + straight from the XAML. Some code-behind may still be necessary, but it should be restricted to view-related + activities, such as starting and stopping animations or communicating with the user via messages or dialogs. + THIS IS NOT THE WAY WE WILL USE. + + 2) DialogService - (e.g., see http://www.codeproject.com/Articles/36745/Showing-Dialogs-When-Using-the-MVVM-Pattern) + ViewModel has reference to an interface (or base class) that defines methods for showing modal dialogs. + This option was used so far in this project (via interfaces INotificationView and IConfirmationView). + Adding other more general dialogs would need adding another interface and things are getting difficult + (http://blog.roboblob.com/2010/01/19/modal-dialogs-with-mvvm-and-silverlight-4/). + Disadvantages are that this approach is too tightly coupled. The view-model needs to hold onto a reference to + the object implementing the interface, and classes must implement all members of the interface even the ones + they may not care about. So far we had only one view, so our view implemented easily both interfaces but + imagine having several views. What a redundancy it would be to implement DialogService interfaces in every + view. One could move the implementation into a specialized class (e.g., DialogService) completely separated + from views or to develop abstract base class for views. + THIS IS THE CURRENT WAY. + + 3) Another approach is to use a message bus to handle all communication between view and view-model. + (see e.g., http://www.codeproject.com/Articles/35277/MVVM-Mediator-Pattern) + This is, however, too loosely couples and it is difficult to understand to code for newcomers. It is the + way adopted by Qt library. Generally speaking, this sort of mediation is effective at allowing tests and + promoting some degree of flexibility, but you still have the same dependency in concept, and then you have + the mediator code to maintain and manage. Testing such a mediator is difficult :-) + THIS IS THE WAY WE WILL PREFER NOT TO USE. + + 4) An event-based communication model - Events have finer granularity than interfaces because subscribers can + pick and choose which events to subscribe to, and the publisher is relieved from the burden of maintaining direct + references to subscribers. In addition, the view-model is not aware that its displaying a dialog. + Using events also alleviates the need for an intermediary such as a message bus. Because the view already + has a reference to a view-model, it can easily subscribe to events and specify a callback method. In the + callback the view can show information to the user and get a response in any way it wants. + THIS IS THE WAY WE MAY USE IN THE FUTURE. + +* Design notes: + 1) All the functionality of the application is to be split into several Workspaces (also elsewhere known as + Layouts - see, e.g., Prims: http://msdn.microsoft.com/en-us/library/ff921098(v=pandp.20).aspx): + a) MainWorkspace - is composed of two Views (and associated ViewModels): + a.1) StudentsFilterView - allows specification of user defined filters and sort criteria + a.2) StudentsListView - displays a given list of students and for them some summary (number of students selected, ...) + + allows commands such as AddNewStudent (with the option to add empty evaluations + based on the category structure), ... + b) StudentWorkspace - is also composed of two Views (and associated ViewModels): + b.1) StudentDetailsView - allows modification of the personal data stored for the given student + b.2) StudentEvaluationView - allows modification of evaluations for every category (+ allows commands such as + AddNewCategory, with the option to add automatically new evaluations for every student), presented as + a tree of categories with editable items + c) EvaluationWorkspace - is also composed of two Views (and associated ViewModels): + c.1) CategoryFilterView - allows selection of categories whose evaluations are to be edited + c.2) EvaluationListView - grid view allowing editing evaluations for every student (from the given list) + d) CategoryWorkspace - is composed of a single View (and associated ViewModels): + d.1) CategoryDetailsView - allows modification of the category data stored for the given category + 2) ViewModels implements INotifyPropertyChanged to notify observer of any change of their properties, + no matter if it is a property related to the representation / manipulation with the data (such as IsSelected), + or the model property (e.g., FirstName) and business logic. Whenever it makes sense, we will have a ViewModel + for a single item and then for collections, e.g., we will have StudentsListViewModel as well as + StudentsListItemViewModel (and these ViewModels will be mapped to Views via bindings). + +* Created several interfaces to support rich variety of ViewModels, e.g., ViewModels encapsulating both read-only + simple models (IViewModel), editable models (IEditableViewModel), models that may be selected and focused + (ISelectableViewModel), ViewModels encapsulating lists of items (IListViewModel) that may be selected and focused + (ISelectableListViewModel), and created implementation of three key ViewModels: StudentViewModel (see b.1), + StudentListItemViewModel (an item in the list) and StudentListViewModel (list of StudentListItemViewModel + for StudentsListView - see a.2) + + Advantage: Separation of ViewModel implementation from Views, which allows using one View with different ViewModels + Disadvantage: ViewModel is becoming quite complex + Discussion: Reusing one View for different unrelated ViewModels is rare, and if it happens one may easily + use Refactor to extract Interface from existing ViewModel class to support it, so such extensive using of + interfaces is good only for tutorial purpose. Furthermore, with DataBinding (as in WPF, WinForms, ...) + Views can process different ViewModels even if they do not share some common interface but all that have in common + is "object". It is therefore, useful to reduce the complexity by removing interfaces. + +* Commands provided by a ViewModel for execution (such as DeleteStudent, CreateNewStudent, ...) are now + accessible via standard ICommand interface (using own implementation in RelayCommand) + + Advantages: ICommand supports also CanExecute, so Views can now display commands options accordingly. + Furthermore, instances of ICommand can be directly data bound to WPF controls (e.g., Buttons). + Associating ICommand with controls of WinForms can be done easily via lambda expressions, e.g., + button.Click += (sender, e) => viewModel.Command.Execute(e). + +* When the data in a ViewModel changes, ViewModel no longer actively calls some View routine (via interface) + to change its representation (e.g., disable a button, refresh a list, ...) but notifies about the change + its listeners via standard INotifyPropertyChanged interface and it is up to listeners (e.g., a View) to + handle the change of the state. + + Advantages: one ViewModel can be presented by multiple unrelated Views since ViewModels now may have none + or only a limited (via some basic interface, e.g., IWindowView) knowledge about their Views. + INotifyPropertyChanged can be data bound in WPF directly to controls without a necessity to + write any code. + Disadvantages: performance is reduced in the following scenario: ViewModel contains properties A, B such + that B depends on A, View displays both properties. When property A changes, the View + is notified about it, and updates its presentation of A, which may require running complex layout + algorithm, but as A influences B, the View is then notified about the change of B, which + require running the same layout algorithm once again. + +* Implementation of INotificationView and IConfirmationView for console was moved into concrete classes (NotificationView + and ConfirmationView) and these classes are accessed (via interfaces) from ViewModelBase. + +* DialogService was implemented to support separation of Views and ViewModels. Application uses DialogService singleton + to register a common View interface (currently IWindowView, INotificationView and IConfirmationView) implemented by + some concrete View in such a manner that the concrete implementation is identified by the interface it implements + and by a unique identifier. When ViewModel needs to access directly View (e.g., to show dialog, ...), it asks + DialogService to provide it with reference to the View specifying the interface to retrieve and the id. DialogService + creates the instance of the View (unless it is already instanced singleton) and returns it (cast to the common + View interface) to ViewModel. + + Advantages: separation of Views and ViewModels; a ViewModel can be constructed without a necessity to pass + it a reference to its View (which could be difficult to maintain). + +* Designed IWindowView interface containing the fundamental properties and routines for Views (of Window style): + object DataContext - reference to ViewModel instance that is being represented by the View, + methods Show, Close - Show non-modal window / close it + DialogResult and ShowDIalog - Show modal window and provides the result (true = OK, false = Cancel, null = Error) + + Note: IWindowView interface is designed to be compatible with WPF Window class, i.e., WPF based Views just + need to specify that they implement IWindowView but the implementation is done by Microsoft in their + base class (Window) + +* Added an automatic validation of ViewModels based on annotation attributes of wrapped Model. Validation support + implements IDataErrorInfo interface, which is standard very old interface designed for this purpose. + + Advantage: IDataErrorInfo is exploited in WPF automatically to validate values in controls. + +* Problems and TODO: + Subdivide the project into two or three projects: Core (everything without View implementation) and + ConsoleApp (View implementation) + + +7.10.2013 - Local Database +* Added migrations (Tools/Library Package Manager/Package Manager Console: enable-migrations) + enabled automatic updates + Advantage: When the application is run it should update automatically the database to the latest version + (see http://msdn.microsoft.com/en-us/data/jj591621). Problem is that AutoMigrations seems not + working correctly in many cases yielding various errors such as "Data loss". + +* Specified the Connection string to local database (see App.config) + Advantage: we control which database should be used and where it should be located. + +* POCO classes updated so that their members are annotated by Data attributes + + Advantage: this allows an easy specification of various constraints (e.g., Personal number may not be null). + +* Added custom validation such as "Personal number" must be unique and the number of points cannot exceed the maximum. + + +4.10.2013 - Code-First Simple but Working Entity Framework + +* ViewModel modified to a better design: Create and Edit no longer does the changes into the repository but only + marks the object as Added or Modified and until AcceptChanges or CancelChanges is called changes are purely local. + + Advantage: this corresponds better to how the user interacts with the application: the user clicks on button + "Add" and gets some GUI where they fill the appropriate data and after that they click either OK + to confirm their edits or Cancel to abort the changes. + Note: This solves the problem 8. + Problem: 9) Cancel has no effect for local (in-memory) objects, i.e., it fails to do anything for repositories + +* AcceptChanges calls Save to transfer the entity data into the database + Note: This is solution (b) to the problem 6 + Problem: 10) For Xml repository, this solution actually means that after a single change, the whole repository + is serialized into a Xml file, which harms the performance as this is required to be done only once. + As Xml repository is supposed to be used for "Test" purposes only, this problem is not to be saved! + +* Lazy loading enabled for Evaluations + StudentViewModel.DisplayList updated to adopt eager loading. + Note: This solves the problem 7 + + + +4.10.2013 - Code-First Simple Entity Framework + +* Added Entity Framework and classes DbRepository, DbStudentEvaluationContext and DbStudentEvaluationUnitOfWork + + IMPORTANT STEPS TO REPRODUCE: Entity Framework is no longer a corporal part of .NET but rather a package being + developed as standalone => (from the context project or solution menu launch "Manage NuGet Packages ..." + and install EntityFramework, which will create/modify app.config and packages.config) + + Note: Compare DbStudentEvaluationUnitOfWork and LocalStudentEvaluationUnitOfWork + Compare DbStudentEvaluationContext and LocalStudentEvaluationContext + Compare DbRepository and LocalRepository + + Problems: 6) Changes are not visible until Save. The reason is that DbRepository.Get retrieves data from + database but not from memory, so it cannot retrieve unsaved changes. Possible solutions are: + + a) Load all data from the database into memory and modify DbRepository.Get to work with .Local + datasets (contains loaded and newly added objects). + + Advantage: everything is local, i.e., it would work fine + + Disadvantage: loading everything into memory is both time and memory consuming + changes done + by other users are not reflected until the application is reloaded. Risk of problems. + + b) ViewModel should call Save after a change is complete + + Advantages: Lower risk of concurrency problems, suitable for MVC where objects are detached + Disadvantage: Any change is stored immediately - ?can this be altered by using transactions? + + 7) When retrieved from database Evaluations in students and categories are empty and similarly + Student and Category in evaluations are null (unless Students and Categories are loaded first). + The reason for this behaviour is that currently POCO classes and repositories are configured + to use Explicit loading model, i.e., you need to call Load to get related objects into the memory. + To make it work, however, would also need to add Foreign Keys CustomerId and StudentId into Evaluation + class. There are two other loading models: + a) Eager loading - it is necessary to specify what should be retrieved automatically from DB in + includeProperties array of method Get. It is not recommended mode if you retrieve lot of stuff but + not using it (large and complex single query). + b) Lazy loading - used by default if "virtual" is added to Evaluations collections definition in + Student and Category (EF replace List<> by its own implementation). Not recommended, if you need + really all related data (multiple queries instead only one). + + 8) Current View and ViewModel does not contain "ApplyChanges" after "EditDetail" was called, + so it makes the update operation not working at all. + +* Problems 2 and 5 solved automatically by switching to EF + +3.10.2013 - Xml Persistent Data Context + +* Added XmlStudentEvaluationContext concrete class of data context that supports serialization of repositories + to external Xml file (stored on a local disk). + + Note: This demonstrates how easily you can change something without necessity to modify many classes. + XmlSerializer and also App.config configurations are also demonstrated. + +* Fixed problem with Id not automatically assigned + +3.10.2013 - UnitOfWork pattern + +* Added generic IRepository and IUnitOfWork with the default in-memory only implementation + + Note: see Repository_pattern_diagram.png; Id has been added into every POCO class + Advantage: presentation layer does not access data context directly but through multiple repositories + of the same interface that are grouped into a unit of work that is responsible for + construction of these repositories above the same underlying data context. As a result, + switching from a database data context to in-memory only data context requires only + a change of one line where the object of concrete unit of work is constructed. The rest + is automatic thanks to interfaces used. + + Problems: 5) Adding Evaluation should automatically add the reference to collections of referenced + Category and Student. This problem is not simple to solve [lot of code] and as EF data context + takes automatically care of this, we are not going to solve this problem and leaving it for + caller to fill POCO classes correctly. + +* Problems 1 and 4 from CRUD business logic has been fixed. + + +2.10.2013 - CRUD business logic + +* Added StudentRepository (implements IStudentRepository) with the Create, Read, Update and Delete business logic + + Advantage: presentation layer does not access data context (StudentEvaluationContext) directly but through + IStudentRepository, which enables changing of the data context (e.g., we may have in-memory only + or persistent data contexts and the latter may be via XML serialization, binary file stream, + SharePoint, Entity Framework database, ...) without changing the presentation layer. + + Problems: 1) GetStudents method reads all students from the physical data context (e.g., from disk) + into memory but its caller typically filters this list using LINQ to get only relevant data. + If data context represents a database context and the database is remote, this actually means + that lot of data must be transferred via the network just to be then discarded. + + This problem may be solved easily by specifying filters as parameters of the method. + see http://www.asp.net/mvc/tutorials/getting-started-with-ef-using-mvc/implementing-the-repository-and-unit-of-work-patterns-in-an-asp-net-mvc-application + + 2) As StudentEvaluationContext and StudentRepository represents in-memory only data, + it provides the caller with the original objects so that any change in the object is + immediately seen within the data context and it cannot be undone. + + This problem will be ignored since we are targeting at EF where this is solved automatically. + Solving this problem with in-memory only data would require cloning objects and tracking changes, + which is too difficult for the purpose of this tutorial. + +* Added View and ViewModel to support MVVM software pattern + + Note: Three software patterns are currently commonly used in practice, each of them aiming at forming + various layers in the application with minimal dependency so that the code in any layer can be easily + replaced by another code without the necessity to change the code in other layers. + 1) Model View Presenter (MVP) = Presenter represent the main core of the application, it knows the + model (with its Data Abstract Layer - DAL) and also Views (User UI), formatting the data of the model + for Views to visualize them to the user, whilst accepting the calls from Views triggered by the user + action to get the data from the Views and update the Model according to these changes. MVP is + suitable form MFC and WinForms applications. For example, each form has public properties to get/set + and method ShowDialog (all these coming from some interface) and its implementation of ShowDialog + transfers (e.g., via data binding) its public properties to some properties of controls it displays + and when the form is closed with OK or Apply, values from controls are transferred to these public + properties. Presenter has access to the repository so it can take the model object, transfer its + relevant properties to properties of a form (via the interface) and calling "ShowDialog". If the + user wants to accept their changes, the model object is modified accordingly and optionally saved + immediately into the underlying physical repository. + 2) Model View Controller (MVC) = Similar to MVP, designed for web applications, controller gets + the requests from the user (via web browser) and provides the user with passive views. Actually, + the only important difference between MVC and MVP is that the method of the controller exits + before the user interaction with the view takes its place and another method of the controller is + called after the user interaction is done. It is IIS (or another web server) that communicates + with the controller. + 3) Model View ViewModel (MVVM) = Similar to MVP with one major difference as follows. As View in MVP + contains some code for transferring the properties to UI controls and back, there is a potential + risks of bugs requiring some testing (e.g., when nothing is selected in the list, is the button + "Delete" really disabled?). However, these tests cannot be automated. MVVM comes with a special remedy + to this problem: MVVM Views are striped of this logic and this is transferred to Presenter that is + renamed to ViewModel to reflect new added functionality. This means that ViewModel has to provide + means to select the items (that will be in the list) and property to count the number of selected + items, which is in MVP pattern handled by controls of View. These properties are mapped to View + controls via binding (provided by Microsoft). As it is, only proper binding must be checked for Views, + and ViewModels may by automatically tested from code. MVVM is designed for WPF. + + IMPORTANT: There is no strict rule how to write MVVM (MVC, MVP) applications and as a result you may + find many different ways how MVVM was applied. Some developers prefer to have a simple solution and, + therefore, their Model (entity) class will contain also custom logic, notification support, etc., + whilst the other will create another class (on ViewModel level) for this purpose and let the Model + class to be truly a POCO class. Similarly one would create a ViewModel class that would contain all + the logic whilst the other would split it into several classes and have a specialized ViewModel even + for the item in a list to be displayed. Furthermore, as large solutions often target at multiple + platforms, e.g., desktop app and internet app, there is no universal pattern for them and, therefore, + trying to reuse as much code as possible, these application often have classes that are not placed + in any category. + + Advantage: StudentView that currently shows the information about students onto the console can be replaced + by more sophisticated GUI without any change of business logic, e.g., finding the item to delete, + confirmation of the deletion by the user, and the deletion itself is unchanged. + + Problems: 3) Currently the pattern resembles MVP instead of MVVM despite the names. This is only a formalism, + however. True MVVM will be there once Console application is replaced by WPF desktop application. + +* Category and Evaluation is ignored for now, construction of CRUD objects would be similar. + Problems: 4) While Student have unique key (PersonalNumber), and for Category we may consider this to be its Name, + for Evaluation nothing like this exists (one may use Student.PersonalNumber + Category.Name but it + is a bit awkward). Solution would be to introduce some internal identifier, e.g., int Id and for + consistency the same could be done for Student and Category (working with strings is slower than + working with integers). + +1.10.2013 - POCO (Plain Old CLR Object) classes for Student, Category and Evaluation +30.7.2013 - Blank solution \ No newline at end of file diff --git a/Documentation.shfbproj b/Documentation.shfbproj new file mode 100644 index 0000000..47782d1 --- /dev/null +++ b/Documentation.shfbproj @@ -0,0 +1,44 @@ + + + + + Debug + AnyCPU + 2.0 + {dbe0c8a0-36f4-4c5b-8315-95759433cb99} + 1.9.9.0 + + Documentation + Documentation + Documentation + + .NET Framework 4.0 + .\Help\ + Documentation + en-US + + + + + + + + + + + + + + + + + + + + + + + + \ No newline at end of file diff --git a/Help/Documentation.chm b/Help/Documentation.chm new file mode 100644 index 0000000..d0d4707 Binary files /dev/null and b/Help/Documentation.chm differ diff --git a/NETProject.sln b/NETProject.sln index 4b6ff64..ed71121 100644 --- a/NETProject.sln +++ b/NETProject.sln @@ -1,7 +1,51 @@  Microsoft Visual Studio Solution File, Format Version 12.00 # Visual Studio 2012 +Project("{2150E333-8FDC-42A3-9474-1A3956D46DE8}") = "Solution Items", "Solution Items", "{96FBCAD4-21C5-4E84-B896-6F009F9C9DAB}" + ProjectSection(SolutionItems) = preProject + Changes.txt = Changes.txt + README.md = README.md + Specifikace.docx = Specifikace.docx + WorkPlan.txt = WorkPlan.txt + EndProjectSection +EndProject +Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "StudentEvaluatorCore", "StudentEvaluatorCore\StudentEvaluatorCore.csproj", "{5F535E59-C1A7-4766-86F1-BFBF71CB1B37}" +EndProject +Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "StudentEvaluatorConsoleApp", "StudentEvaluatorConsoleApp\StudentEvaluatorConsoleApp.csproj", "{A8D6EB45-A7FD-4977-80F0-11F5E5C1B0AF}" +EndProject +Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "StudentEvaluatorCoreUnitTests", "StudentEvaluatorCoreUnitTests\StudentEvaluatorCoreUnitTests.csproj", "{706E6DD9-981E-4EBD-8445-82B3EF452862}" +EndProject +Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "StudentEvaluatorWPFApp", "StudentEvaluatorWPFApp\StudentEvaluatorWPFApp.csproj", "{1EA3CEFB-0CE3-4E04-A0E5-FB2A38BA4F54}" +EndProject +Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "StudentEvaluatorCoreDesignData", "StudentEvaluatorCoreDesignData\StudentEvaluatorCoreDesignData.csproj", "{8B39D65F-4373-45CF-B8E4-990FEE3BAC4F}" +EndProject Global + GlobalSection(SolutionConfigurationPlatforms) = preSolution + Debug|Any CPU = Debug|Any CPU + Release|Any CPU = Release|Any CPU + EndGlobalSection + GlobalSection(ProjectConfigurationPlatforms) = postSolution + {5F535E59-C1A7-4766-86F1-BFBF71CB1B37}.Debug|Any CPU.ActiveCfg = Debug|Any CPU + {5F535E59-C1A7-4766-86F1-BFBF71CB1B37}.Debug|Any CPU.Build.0 = Debug|Any CPU + {5F535E59-C1A7-4766-86F1-BFBF71CB1B37}.Release|Any CPU.ActiveCfg = Release|Any CPU + {5F535E59-C1A7-4766-86F1-BFBF71CB1B37}.Release|Any CPU.Build.0 = Release|Any CPU + {A8D6EB45-A7FD-4977-80F0-11F5E5C1B0AF}.Debug|Any CPU.ActiveCfg = Debug|Any CPU + {A8D6EB45-A7FD-4977-80F0-11F5E5C1B0AF}.Debug|Any CPU.Build.0 = Debug|Any CPU + {A8D6EB45-A7FD-4977-80F0-11F5E5C1B0AF}.Release|Any CPU.ActiveCfg = Release|Any CPU + {A8D6EB45-A7FD-4977-80F0-11F5E5C1B0AF}.Release|Any CPU.Build.0 = Release|Any CPU + {706E6DD9-981E-4EBD-8445-82B3EF452862}.Debug|Any CPU.ActiveCfg = Debug|Any CPU + {706E6DD9-981E-4EBD-8445-82B3EF452862}.Debug|Any CPU.Build.0 = Debug|Any CPU + {706E6DD9-981E-4EBD-8445-82B3EF452862}.Release|Any CPU.ActiveCfg = Release|Any CPU + {706E6DD9-981E-4EBD-8445-82B3EF452862}.Release|Any CPU.Build.0 = Release|Any CPU + {1EA3CEFB-0CE3-4E04-A0E5-FB2A38BA4F54}.Debug|Any CPU.ActiveCfg = Debug|Any CPU + {1EA3CEFB-0CE3-4E04-A0E5-FB2A38BA4F54}.Debug|Any CPU.Build.0 = Debug|Any CPU + {1EA3CEFB-0CE3-4E04-A0E5-FB2A38BA4F54}.Release|Any CPU.ActiveCfg = Release|Any CPU + {1EA3CEFB-0CE3-4E04-A0E5-FB2A38BA4F54}.Release|Any CPU.Build.0 = Release|Any CPU + {8B39D65F-4373-45CF-B8E4-990FEE3BAC4F}.Debug|Any CPU.ActiveCfg = Debug|Any CPU + {8B39D65F-4373-45CF-B8E4-990FEE3BAC4F}.Debug|Any CPU.Build.0 = Debug|Any CPU + {8B39D65F-4373-45CF-B8E4-990FEE3BAC4F}.Release|Any CPU.ActiveCfg = Release|Any CPU + {8B39D65F-4373-45CF-B8E4-990FEE3BAC4F}.Release|Any CPU.Build.0 = Release|Any CPU + EndGlobalSection GlobalSection(SolutionProperties) = preSolution HideSolutionNode = FALSE EndGlobalSection diff --git a/NETProject.v11.suo b/NETProject.v11.suo deleted file mode 100644 index 7929f3d..0000000 Binary files a/NETProject.v11.suo and /dev/null differ diff --git a/README.md b/README.md index b3251a7..fee0cac 100644 --- a/README.md +++ b/README.md @@ -2,3 +2,14 @@ NETProject ========== .NET Tutorial for KIV/NET + +Tento tutorial je umisten na: +git@github.com:besoft/NETProject.git + +Pro jednotlive lekce je treba v GitExtensions oznacit pozadovany Commit a pres kontextove menu vyvolat +"Checkout revision". + +Cilem tutorialu je postupne vytvorit jak standalone tak webovou aplikaci, ktera bude splnovat zadani dle specifikace +pozadavku, pricemz v prubehu toho demostrovat moznosti C# od nejjednussich veci az po ty slozitejsi. + +Zmeny jsou popsany v soubory Changes.txt \ No newline at end of file diff --git a/Repository_pattern_diagram.png b/Repository_pattern_diagram.png new file mode 100644 index 0000000..db40902 Binary files /dev/null and b/Repository_pattern_diagram.png differ diff --git a/Specifikace.docx b/Specifikace.docx new file mode 100644 index 0000000..7e44c94 Binary files /dev/null and b/Specifikace.docx differ diff --git a/StudentEvaluatorConsoleApp/App.config b/StudentEvaluatorConsoleApp/App.config new file mode 100644 index 0000000..0cae4f7 --- /dev/null +++ b/StudentEvaluatorConsoleApp/App.config @@ -0,0 +1,43 @@ + + + +
+ + +
+ + + + + + + + + + + + + + + + + + + + + + + DarkGreen + + + Blue + + + DarkYellow + + + Red + + + + \ No newline at end of file diff --git a/StudentEvaluatorConsoleApp/Program.cs b/StudentEvaluatorConsoleApp/Program.cs new file mode 100644 index 0000000..ea02b77 --- /dev/null +++ b/StudentEvaluatorConsoleApp/Program.cs @@ -0,0 +1,114 @@ +using System; +using System.Data.Entity.Validation; +using System.Diagnostics.Contracts; +using System.Globalization; +using System.Linq; +using System.Threading; +using Zcu.StudentEvaluator.DAL; +using Zcu.StudentEvaluator.View; +using Zcu.StudentEvaluator.ViewModel; + +namespace Zcu.StudentEvaluator.ConsoleApp +{ + public class BootStraper + { + public static void InitializeIOC() + { + DialogService.DialogService.Default.Register(DialogService.DialogConstants.NotificationView); + DialogService.DialogService.Default.Register(DialogService.DialogConstants.ConfirmationView); + DialogService.DialogService.Default.Register(DialogService.DialogConstants.EditStudentView); + + DialogService.DialogService.Default.Register(); //main View + } + } + + class Program + { + static void Main(string[] args) + { + // Thread.CurrentThread.CurrentUICulture = new System.Globalization.CultureInfo("cs-CZ"); + Thread.CurrentThread.CurrentUICulture = new System.Globalization.CultureInfo("en-US"); + + BootStraper.InitializeIOC(); + + //var confirm = DialogService.DialogService.Default.Get(); + //confirm.ConfirmAction(ConfirmationOptions.OK, null, "Loading ... "); + + + //Pokus(); + var unitOfWork = + //new LocalStudentEvaluationUnitOfWork(); + new DbStudentEvaluationUnitOfWork(); + if (unitOfWork.Categories.Get().FirstOrDefault() == null) + { + try + { + unitOfWork.PopulateWithData(); + } + catch (DbEntityValidationException excValidation) + { + foreach (var item in excValidation.EntityValidationErrors) + { + Console.WriteLine("Validation of '{0}' failed with these errors:", item.Entry.Entity.GetType().Name); + foreach (var err in item.ValidationErrors) + { + Console.WriteLine("For '{0}' : {1}", err.PropertyName, err.ErrorMessage); + } + } + } + } + + var mainViewModel = new StudentListViewModel(unitOfWork); + + var mainView = new StudentListView(); + mainView.DataContext = mainViewModel; + + mainView.ShowDialog(); + + //This is the directory into which the user configuration will be saved + Console.WriteLine(Environment.GetFolderPath(Environment.SpecialFolder.ApplicationData)); + //Console.WriteLine(Application.LocalUserAppDataPath); + + Properties.ColorSettings.Default.SelectionColor = ConsoleColor.DarkCyan; + Properties.ColorSettings.Default.Save(); + } + + + + /* + private static void Pokus() + { + int nextId = 0; + Student[] students = new Student[30]; + for (int i = 0; i < students.Length; i++) + { + var student = new Student(); + student.Id = ++nextId; + student.Surname = (student.Id % 2 == 0) ? "A" : "B"; + student.FirstName = (student.Id % 3 == 0) ? "X" : "Y"; + students[i] = student; + } + + var col = GetStudents2(students, x => x.Id % 2 == 0, x => x.OrderBy(s => s.FullName).ThenByDescending(s => s.Id),"1", "2", "3"); + foreach (var item in col) + { + Console.WriteLine("{0}\t{1}", item.FullName, item.Id); + } + + Console.ReadLine(); + } + + + private static IEnumerable GetStudents2(IEnumerable students, Expression> filter, + Func, IOrderedQueryable> orderBy = null, params string[] includes) + { + foreach (var item in includes) + { + Console.WriteLine("Include({0}).", item); + } + + return orderBy(students.AsQueryable().Where(filter)); + } + * */ + } +} diff --git a/StudentEvaluatorConsoleApp/Properties/AssemblyInfo.cs b/StudentEvaluatorConsoleApp/Properties/AssemblyInfo.cs new file mode 100644 index 0000000..b30d255 --- /dev/null +++ b/StudentEvaluatorConsoleApp/Properties/AssemblyInfo.cs @@ -0,0 +1,36 @@ +using System.Reflection; +using System.Runtime.CompilerServices; +using System.Runtime.InteropServices; + +// General Information about an assembly is controlled through the following +// set of attributes. Change these attribute values to modify the information +// associated with an assembly. +[assembly: AssemblyTitle("StudentEvaluatorConsoleApp")] +[assembly: AssemblyDescription("")] +[assembly: AssemblyConfiguration("")] +[assembly: AssemblyCompany("")] +[assembly: AssemblyProduct("StudentEvaluatorConsoleApp")] +[assembly: AssemblyCopyright("Copyright © 2013")] +[assembly: AssemblyTrademark("")] +[assembly: AssemblyCulture("")] + +// Setting ComVisible to false makes the types in this assembly not visible +// to COM components. If you need to access a type in this assembly from +// COM, set the ComVisible attribute to true on that type. +[assembly: ComVisible(false)] + +// The following GUID is for the ID of the typelib if this project is exposed to COM +[assembly: Guid("83e04826-c079-49fc-b411-1b3439bf928b")] + +// Version information for an assembly consists of the following four values: +// +// Major Version +// Minor Version +// Build Number +// Revision +// +// You can specify all the values or you can default the Build and Revision Numbers +// by using the '*' as shown below: +// [assembly: AssemblyVersion("1.0.*")] +[assembly: AssemblyVersion("1.0.0.0")] +[assembly: AssemblyFileVersion("1.0.0.0")] diff --git a/StudentEvaluatorConsoleApp/Properties/ColorSettings.Designer.cs b/StudentEvaluatorConsoleApp/Properties/ColorSettings.Designer.cs new file mode 100644 index 0000000..9931714 --- /dev/null +++ b/StudentEvaluatorConsoleApp/Properties/ColorSettings.Designer.cs @@ -0,0 +1,74 @@ +//------------------------------------------------------------------------------ +// +// This code was generated by a tool. +// Runtime Version:4.0.30319.34011 +// +// Changes to this file may cause incorrect behavior and will be lost if +// the code is regenerated. +// +//------------------------------------------------------------------------------ + +namespace Zcu.StudentEvaluator.Properties { + + + [global::System.Runtime.CompilerServices.CompilerGeneratedAttribute()] + [global::System.CodeDom.Compiler.GeneratedCodeAttribute("Microsoft.VisualStudio.Editors.SettingsDesigner.SettingsSingleFileGenerator", "11.0.0.0")] + internal sealed partial class ColorSettings : global::System.Configuration.ApplicationSettingsBase { + + private static ColorSettings defaultInstance = ((ColorSettings)(global::System.Configuration.ApplicationSettingsBase.Synchronized(new ColorSettings()))); + + public static ColorSettings Default { + get { + return defaultInstance; + } + } + + [global::System.Configuration.UserScopedSettingAttribute()] + [global::System.Diagnostics.DebuggerNonUserCodeAttribute()] + [global::System.Configuration.DefaultSettingValueAttribute("DarkGreen")] + public global::System.ConsoleColor MessageColor { + get { + return ((global::System.ConsoleColor)(this["MessageColor"])); + } + set { + this["MessageColor"] = value; + } + } + + [global::System.Configuration.UserScopedSettingAttribute()] + [global::System.Diagnostics.DebuggerNonUserCodeAttribute()] + [global::System.Configuration.DefaultSettingValueAttribute("Blue")] + public global::System.ConsoleColor SelectionColor { + get { + return ((global::System.ConsoleColor)(this["SelectionColor"])); + } + set { + this["SelectionColor"] = value; + } + } + + [global::System.Configuration.UserScopedSettingAttribute()] + [global::System.Diagnostics.DebuggerNonUserCodeAttribute()] + [global::System.Configuration.DefaultSettingValueAttribute("DarkYellow")] + public global::System.ConsoleColor WarningColor { + get { + return ((global::System.ConsoleColor)(this["WarningColor"])); + } + set { + this["WarningColor"] = value; + } + } + + [global::System.Configuration.UserScopedSettingAttribute()] + [global::System.Diagnostics.DebuggerNonUserCodeAttribute()] + [global::System.Configuration.DefaultSettingValueAttribute("Red")] + public global::System.ConsoleColor ErrorColor { + get { + return ((global::System.ConsoleColor)(this["ErrorColor"])); + } + set { + this["ErrorColor"] = value; + } + } + } +} diff --git a/StudentEvaluatorConsoleApp/Properties/ColorSettings.settings b/StudentEvaluatorConsoleApp/Properties/ColorSettings.settings new file mode 100644 index 0000000..61b57b5 --- /dev/null +++ b/StudentEvaluatorConsoleApp/Properties/ColorSettings.settings @@ -0,0 +1,18 @@ + + + + + + DarkGreen + + + Blue + + + DarkYellow + + + Red + + + \ No newline at end of file diff --git a/StudentEvaluatorConsoleApp/StudentEvaluatorConsoleApp.csproj b/StudentEvaluatorConsoleApp/StudentEvaluatorConsoleApp.csproj new file mode 100644 index 0000000..694292c --- /dev/null +++ b/StudentEvaluatorConsoleApp/StudentEvaluatorConsoleApp.csproj @@ -0,0 +1,127 @@ + + + + + Debug + AnyCPU + {A8D6EB45-A7FD-4977-80F0-11F5E5C1B0AF} + Exe + Properties + StudentEvaluatorConsoleApp + StudentEvaluatorConsoleApp + v4.5 + 512 + 0 + + + AnyCPU + true + full + false + bin\Debug\ + DEBUG;TRACE + prompt + 4 + False + False + True + False + False + True + True + True + False + False + False + True + True + False + False + False + True + False + True + True + False + True + + + + + + + + True + True + Full + Build + 0 + + + AnyCPU + pdbonly + true + bin\Release\ + TRACE + prompt + 4 + + + + False + ..\packages\EntityFramework.6.0.1\lib\net45\EntityFramework.dll + + + False + ..\packages\EntityFramework.6.0.1\lib\net45\EntityFramework.SqlServer.dll + + + + + + + + + + + + + + + True + True + ColorSettings.settings + + + + + + + + + + Designer + + + SettingsSingleFileGenerator + ColorSettings.Designer.cs + Zcu.StudentEvaluator.Properties + + + + + + + {5f535e59-c1a7-4766-86f1-bfbf71cb1b37} + StudentEvaluatorCore + + + + + \ No newline at end of file diff --git a/StudentEvaluatorConsoleApp/View/ClassDiagram.cd b/StudentEvaluatorConsoleApp/View/ClassDiagram.cd new file mode 100644 index 0000000..2eb819a --- /dev/null +++ b/StudentEvaluatorConsoleApp/View/ClassDiagram.cd @@ -0,0 +1,93 @@ + + + + + + AAAAAAAAAAAAAAQAAAAAAAAAAAAAAAAAAAAAAAAAAAA= + View\ConfirmationView.cs + + + + + + + AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAABAAAAAAA= + View\NotificationView.cs + + + + + + + AAAAAAAAAAAAAgAAAAAAAQAAAAAAAAAAQAAAAAAAAAA= + View\StudentListView.cs + + + + + + + + + + + + QAAAAAAAAAAAAgAAAAAAAQAAAAAAIAAARAAAAAAAAAA= + View\StudentView.cs + + + + + + + + + kAAAAAAAICQIAgAAAAAAAAEAAAAEAAAAQAAAAACACAA= + View\WindowView.cs + + + + + + + AAAAAAAAAAAAAAQAAAAAAAAAAAAAAAAAAAAAAAAAAAA= + View\IConfirmationView.cs + + + + + + AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAABAAAAAAA= + View\INotificationView.cs + + + + + + kAAAAAAAAAAAAAAAAAAAAAEAAAAEAAAAAAAAAACAAAA= + View\IWindowView.cs + + + + + + AAAAAAAAAAEAAEAAAAAAAAAAAAAAAAgASAABAgACAAA= + View\IConfirmationView.cs + + + + + + AAAAAAAgSAAQACDAAAAAAAAAACAAQAAAAAABAAAAAAA= + View\IConfirmationView.cs + + + + + + AAAAEAAAAAAAAQAAAAAAABAAAAAAAAAAAAAAIAAAAAA= + View\INotificationView.cs + + + + \ No newline at end of file diff --git a/StudentEvaluatorConsoleApp/View/ConfirmationView.cs b/StudentEvaluatorConsoleApp/View/ConfirmationView.cs new file mode 100644 index 0000000..5948e8b --- /dev/null +++ b/StudentEvaluatorConsoleApp/View/ConfirmationView.cs @@ -0,0 +1,98 @@ +using System; + +namespace Zcu.StudentEvaluator.View +{ + public class ConfirmationView : IConfirmationView + { + #region IConfirmationView + /// + /// Confirms the action to be done. + /// + /// Options available during the confirmation. + /// The caption, i.e., a short summary of what is needed to be confirmed. + /// The detailed explanation of what is to be confirmed. + /// + /// User decision. + /// + public ConfirmationResult ConfirmAction(ConfirmationOptions options, string caption, string message) + { + Console.WriteLine("----->\n{0}\n\n{1}\n", caption, message); + while (true) + { + if (options.HasFlag((ConfirmationOptions)ConfirmationResult.Abort)) + Console.Write("(A)bort "); + + if (options.HasFlag((ConfirmationOptions)ConfirmationResult.Retry)) + Console.Write("(R)etry "); + + if (options.HasFlag((ConfirmationOptions)ConfirmationResult.Ignore)) + Console.Write("(I)gnore "); + + if (options.HasFlag((ConfirmationOptions)ConfirmationResult.OK)) + Console.Write("(O)K "); + + if (options.HasFlag((ConfirmationOptions)ConfirmationResult.Yes)) + Console.Write("(Y)es "); + + if (options.HasFlag((ConfirmationOptions)ConfirmationResult.YesToAll)) + Console.Write("YesTo(A)ll "); //Abort is not used with YesToAll + + if (options.HasFlag((ConfirmationOptions)ConfirmationResult.No)) + Console.Write("(N)o "); + + if (options.HasFlag((ConfirmationOptions)ConfirmationResult.NoToAll)) + Console.Write("No(T)oAll "); + + if (options.HasFlag((ConfirmationOptions)ConfirmationResult.Cancel)) + Console.Write("(C)ancel "); + + Console.WriteLine(); + switch (Char.ToUpper(Console.ReadKey(true).KeyChar)) + { + case 'A': + if (options.HasFlag((ConfirmationOptions)ConfirmationResult.Abort)) + return ConfirmationResult.Abort; + if (options.HasFlag((ConfirmationOptions)ConfirmationResult.YesToAll)) + return ConfirmationResult.YesToAll; + break; + + case 'R': + if (options.HasFlag((ConfirmationOptions)ConfirmationResult.Retry)) + return ConfirmationResult.Retry; + break; + + case 'I': + if (options.HasFlag((ConfirmationOptions)ConfirmationResult.Ignore)) + return ConfirmationResult.Ignore; + break; + + case 'O': + if (options.HasFlag((ConfirmationOptions)ConfirmationResult.OK)) + return ConfirmationResult.OK; + break; + + case 'Y': + if (options.HasFlag((ConfirmationOptions)ConfirmationResult.Yes)) + return ConfirmationResult.Yes; + break; + + case 'N': + if (options.HasFlag((ConfirmationOptions)ConfirmationResult.No)) + return ConfirmationResult.No; + break; + + case 'T': + if (options.HasFlag((ConfirmationOptions)ConfirmationResult.NoToAll)) + return ConfirmationResult.NoToAll; + break; + + case 'C': + if (options.HasFlag((ConfirmationOptions)ConfirmationResult.Cancel)) + return ConfirmationResult.Cancel; + break; + } + } + } + #endregion + } +} diff --git a/StudentEvaluatorConsoleApp/View/NotificationView.cs b/StudentEvaluatorConsoleApp/View/NotificationView.cs new file mode 100644 index 0000000..f116c74 --- /dev/null +++ b/StudentEvaluatorConsoleApp/View/NotificationView.cs @@ -0,0 +1,41 @@ +using System; + +namespace Zcu.StudentEvaluator.View +{ + public class NotificationView : INotificationView + { + #region INotificationView + /// + /// Displays the notification message to the user. + /// + /// The type of the notification. + /// The caption of the message, i.e., this is a short summary of what has happened. + /// The message to be displayed containing the detailed explanation of what has happened. + /// The exception containing all the details (may be null). + public void DisplayNotification(NotificationType type, string caption, string message, Exception exc = null) + { + var oldColor = Console.ForegroundColor; + switch (type) + { + case NotificationType.Message: + Console.ForegroundColor = Properties.ColorSettings.Default.MessageColor; break; + case NotificationType.Warning: + Console.ForegroundColor = Properties.ColorSettings.Default.WarningColor; break; + default: + Console.ForegroundColor = Properties.ColorSettings.Default.ErrorColor; break; + } + + Console.WriteLine("{0} : {1}\n\n{2}", type.ToString().ToUpper(), caption, message); + + if (exc != null) + { + Console.WriteLine("\nException:"); + Console.WriteLine(exc.ToString()); + } + + Console.WriteLine(); + Console.ForegroundColor = oldColor; + } + #endregion + } +} diff --git a/StudentEvaluatorConsoleApp/View/StudentListView.cs b/StudentEvaluatorConsoleApp/View/StudentListView.cs new file mode 100644 index 0000000..c338c2b --- /dev/null +++ b/StudentEvaluatorConsoleApp/View/StudentListView.cs @@ -0,0 +1,119 @@ +using System; +using System.Collections.Generic; +using System.Text; +using System.Linq; +using System.Text.RegularExpressions; +using Zcu.StudentEvaluator.DAL; +using Zcu.StudentEvaluator.Model; +using Zcu.StudentEvaluator.ViewModel; +using System.Diagnostics.Contracts; + +namespace Zcu.StudentEvaluator.View +{ + public class StudentListView : WindowView + { + /// + /// Displays the list of students. + /// + /// The list of students. + public void Display(IEnumerable students) + { + Contract.Requires(students != null); + + Console.WriteLine("---------------------------------"); + foreach (var st in students) + { + var oldBkColor = Console.BackgroundColor; + var oldFgColor = Console.ForegroundColor; + + if (st.IsSelected) + Console.BackgroundColor = Properties.ColorSettings.Default.SelectionColor; + + if (st.IsFocused) + Console.ForegroundColor = ConsoleColor.White; + + Console.WriteLine("{0}, {1} {2} - evaluations: {3}", + st.PersonalNumber, st.Surname, st.FirstName, st.Evaluations.Count); + + Console.BackgroundColor = oldBkColor; + Console.ForegroundColor = oldFgColor; + } + Console.WriteLine("---------------------------------"); + } + + /// + /// Displays the content of the window onto the console. + /// + protected override void DisplayContent() + { + var studentListViewModel = this.DataContext as IStudentListViewModel; + Console.WriteLine(studentListViewModel.DisplayName); + Display(studentListViewModel.Items); + Console.WriteLine("Total students: " + studentListViewModel.AllStudentsCount); + Console.WriteLine(); + } + + /// + /// Displays the command menu, gets the next command from the user and processes it + /// + protected override void DispatchCommand() + { + var studentListViewModel = this.DataContext as IStudentListViewModel; + var sb = new StringBuilder(); + + if (studentListViewModel.Items.Count != 0) + sb.Append("(S)elect, "); + + if (studentListViewModel.CreateCommand.CanExecute(null)) + sb.Append("(C)reate, "); + + if (studentListViewModel.EditCommand.CanExecute(null)) + sb.Append("(E)dit, "); + + if (studentListViewModel.DeleteCommand.CanExecute(null)) + sb.Append("(D)elete, "); + + if (studentListViewModel.RefreshListCommand.CanExecute(null)) + sb.Append("(R)efresh, "); + + sb.Append("E(x)it"); + + switch (GetNextCommand(sb.ToString())) + { + case 'S': + { + var pn = GetValue("personal number"); + var item = studentListViewModel.Items.FirstOrDefault(x => x.PersonalNumber == pn); + if (item != null) + { + //we support single selection only + foreach (var it in studentListViewModel.SelectedItems.ToList()) + { + it.IsSelected = false; + } + + item.IsFocused = true; + item.IsSelected = true; + } + } + break; + + case 'C': + studentListViewModel.CreateCommand.Execute(null); + break; + case 'R': + studentListViewModel.RefreshListCommand.Execute(null); + break; + case 'E': + studentListViewModel.EditCommand.Execute(null); + break; + case 'D': + studentListViewModel.DeleteCommand.Execute(null); + break; + case 'X': + this.Close(); + break; + } + } + } +} diff --git a/StudentEvaluatorConsoleApp/View/StudentView.cs b/StudentEvaluatorConsoleApp/View/StudentView.cs new file mode 100644 index 0000000..e8c94df --- /dev/null +++ b/StudentEvaluatorConsoleApp/View/StudentView.cs @@ -0,0 +1,128 @@ +using System; +using System.Collections.Generic; +using System.ComponentModel; +using System.Diagnostics.Contracts; +using System.Text; +using System.Text.RegularExpressions; +using Zcu.StudentEvaluator.DAL; +using Zcu.StudentEvaluator.Model; +using Zcu.StudentEvaluator.ViewModel; + +namespace Zcu.StudentEvaluator.View +{ + public class StudentView : WindowView + { + /// + /// Displays the specified student. + /// + /// The student whose details are to be displayed. + /// if set to false the student data is displayed for read-only. + protected void Display(IStudentViewModel student, bool displayForEdit = true) + { + Contract.Requires(student != null); + + Console.WriteLine("---------------------------------"); + Console.WriteLine("{1}ersonalNumber:\t{0}", student.PersonalNumber, displayForEdit ? "(P)" : "P"); + if (displayForEdit) DisplayValidationError("PersonalNumber"); + + Console.WriteLine("{1}irstName:\t{0}", student.FirstName, displayForEdit ? "(F)" : "F"); + if (displayForEdit) DisplayValidationError("FirstName"); + + Console.WriteLine("{1}urname:\t{0}", student.Surname, displayForEdit ? "(S)" : "S"); + if (displayForEdit) DisplayValidationError("Surname"); + + Console.WriteLine("---------------------------------"); + if (displayForEdit) DisplayValidationError(); + } + + /// + /// Displays the content of the window onto the console. + /// + protected override void DisplayContent() + { + var viewModel = this.DataContext as IStudentViewModel; + Console.WriteLine(viewModel.DisplayName); + Display(viewModel, !viewModel.IsReadOnly); + Console.WriteLine(); + } + + /// + /// Displays the command menu, gets the next command from the user and processes it + /// + protected override void DispatchCommand() + { + var errorInfo = this.DataContext as IDataErrorInfo; + var viewModel = this.DataContext as IViewModel; + var studentViewModel = this.DataContext as IStudentViewModel; + var viewModelCommands = this.DataContext as IEditableViewModel; + + var sb = new StringBuilder(); + if (viewModel.IsReadOnly) + { + //change vs. cancel + if (viewModelCommands.EditCommand.CanExecute(null)) + sb.Append("(E)dit, "); + } + else + { + sb.Append("Edit (P)(F)(S), "); + if (viewModelCommands.SaveCommand.CanExecute(null)) + sb.Append("(O)K, "); + } + + sb.Append("(C)ancel"); + + switch (GetNextCommand(sb.ToString())) + { + case 'E': + viewModelCommands.EditCommand.Execute(null); + break; + case 'P': + studentViewModel.PersonalNumber = GetPersonalNumber(); + break; + case 'F': + studentViewModel.FirstName = GetFirstName(); + break; + case 'S': + studentViewModel.Surname = GetSurname(); + break; + case 'O': + viewModelCommands.SaveCommand.Execute(null); + if (!viewModel.IsModelDirty) + this.DialogResult = true; + break; + case 'C': + viewModelCommands.CancelCommand.Execute(null); + this.DialogResult = false; + break; + } + } + + /// + /// Gets the surname from the user. + /// + /// Surname of the person + private string GetSurname() + { + return GetValue("surname"); + } + + /// + /// Gets the first name from the user. + /// + /// First name of the person + private string GetFirstName() + { + return GetValue("first name"); + } + + /// + /// Gets the personal number from the user. + /// + /// Personal number + private string GetPersonalNumber() + { + return GetValue("personal number"); + } + } +} diff --git a/StudentEvaluatorConsoleApp/View/WindowView.cs b/StudentEvaluatorConsoleApp/View/WindowView.cs new file mode 100644 index 0000000..ce30854 --- /dev/null +++ b/StudentEvaluatorConsoleApp/View/WindowView.cs @@ -0,0 +1,201 @@ +using System; +using System.Collections.Generic; +using System.ComponentModel; +using System.Diagnostics.Contracts; +using System.Linq; +using System.Text; +using System.Text.RegularExpressions; +using System.Threading.Tasks; + +namespace Zcu.StudentEvaluator.View +{ + /// + /// Abstract class for Console Windows + /// + public abstract class WindowView : IWindowView, IDisposable + { + #region Fields + protected bool? _dialogResult = null; //dialog result + protected bool _closed = false; //denotes, if the Window is Closed + #endregion + + /// + /// Gets or sets the data context. + /// + /// + /// The data context implementing IViewModel and other interfaces. + /// + public object DataContext { get; set; } + + /// + /// Opens a window and returns without waiting for the newly opened window to close. + /// + public void Show() + { + DisplayContent(); + } + + /// + /// Manually closes a Window. + /// + public void Close() + { + _closed = true; + } + + /// + /// Gets or sets the dialog result value, which is the value that is returned from the ShowDialog method. + /// + /// + /// A Nullable value of type Boolean. The default is false. + /// + public bool? DialogResult + { + get + { + return _dialogResult; + } + set + { + if (this._dialogResult != value) + { + this._dialogResult = value; + if (!this._closed) + { + this.Close(); + return; + } + } + } + } + + /// + /// Opens a window and returns only when the newly opened window is closed. + /// + /// A Nullable value of type Boolean that specifies whether the activity was accepted (true) or cancelled (false). + /// The return value is the value of the DialogResult property before a window closes. + public bool? ShowDialog() + { + while (!this._closed) + { + DisplayContent(); + DispatchCommand(); + } + + return _dialogResult; + } + + /// + /// Displays the content of the window onto the console. + /// + protected abstract void DisplayContent(); + + /// + /// Displays the command menu, gets the next command from the user and processes it + /// + protected abstract void DispatchCommand(); + + /// + /// Performs application-defined tasks associated with freeing, releasing, or resetting unmanaged resources. + /// + public void Dispose() + { + this.Close(); + } + + /// + /// Gets the next command. + /// + /// The menu command string. + /// Displays menuCommandString and waits for user valid response. Valid keys are given in menuCommandString + /// in brackets, e.g., "E(x)it". The method is not case sensitive, i.e., pressing 'x' and 'X' triggers the same command. + /// Capitalized letter of the command, e.g., 'X' + protected char GetNextCommand(string menuCommandString) + { + Contract.Requires(menuCommandString != null); + + //parse menuCommandString + Regex rex = new Regex(@"\((.)\)"); + var matches = rex.Matches(menuCommandString); + char[] validChars = new char[matches.Count]; + for (int i = 0; i < validChars.Length; i++) + { + validChars[i] = Char.ToUpper(matches[i].Groups[1].Value[0]); + } + + while (true) + { + Console.WriteLine("MENU: {0}", menuCommandString); + char pressed = Char.ToUpper(Console.ReadKey(true).KeyChar); + + if (Array.IndexOf(validChars, pressed) >= 0) + return pressed; //it exists, so return + } + } + + /// + /// Gets the value for the variable from the user. + /// + /// The variable name. + /// if set to true, the user may enter null value, otherwise they must specify valid value. + /// The value + protected string GetValue(string variable, bool canBeNull = false) + { + while (true) + { + Console.Write("Enter " + variable + ": "); + string value = Console.ReadLine(); + + if (value != null) + { + if ((value = value.Trim()).Length == 0) + value = null; + } + + if (value == null && !canBeNull) + Console.WriteLine("{0} cannot be null.", variable); + else + return value; + } + } + + /// + /// Displays the validation error (if available). + /// + /// Name of the property. + protected void DisplayValidationError(string propertyName) + { + IDataErrorInfo errorInfo = this.DataContext as IDataErrorInfo; + if (errorInfo == null) + return; + + var errorMsg = errorInfo[propertyName]; + if (errorMsg != String.Empty) + { + var oldColor = Console.ForegroundColor; + Console.ForegroundColor = ConsoleColor.Red; + Console.WriteLine("* " + errorMsg); + Console.ForegroundColor = oldColor; + } + } + + /// + /// Displays the validation error (if available). + /// + protected void DisplayValidationError() + { + IDataErrorInfo errorInfo = this.DataContext as IDataErrorInfo; + if (errorInfo == null) + return; + + var errorMsg = errorInfo.Error; + if (errorMsg != String.Empty) + { + var oldColor = Console.ForegroundColor; + Console.ForegroundColor = ConsoleColor.Red; + Console.WriteLine("* " + errorMsg); + Console.ForegroundColor = oldColor; + } + } + } +} diff --git a/StudentEvaluatorConsoleApp/packages.config b/StudentEvaluatorConsoleApp/packages.config new file mode 100644 index 0000000..0396cb0 --- /dev/null +++ b/StudentEvaluatorConsoleApp/packages.config @@ -0,0 +1,4 @@ + + + + \ No newline at end of file diff --git a/StudentEvaluatorCore/DAL/DbRepository.cs b/StudentEvaluatorCore/DAL/DbRepository.cs new file mode 100644 index 0000000..94c9afd --- /dev/null +++ b/StudentEvaluatorCore/DAL/DbRepository.cs @@ -0,0 +1,145 @@ +using System; +using System.Collections.Generic; +using System.Data; +using System.Data.Entity; +using System.Diagnostics.Contracts; +using System.Linq; +using System.Linq.Expressions; +using Zcu.StudentEvaluator.Model; + +namespace Zcu.StudentEvaluator.DAL +{ + /// + /// Generic repository of entities + /// + public class DbRepository : IRepository + where TEntity: class, IEntity, new() + { + /// + /// The data context to be worked with + /// + protected DbStudentEvaluationContext Context { get; private set; } + + /// + /// The collection containing the data for TEntity + /// + protected DbSet Items {get; private set; } + + + /// + /// Initializes a new instance of the class. + /// + /// The context of this repository. + public DbRepository(DbStudentEvaluationContext context) + { + Contract.Requires(context != null); + + this.Context = context; + + //discover Items in the context + this.Items = this.Context.Set(); + } + + + /// + /// Gets the collection of all items. + /// + /// The filter specification - see remarks. + /// The order by specification - see remarks. + /// The include properties - see remarks. + /// See IRepository + /// + /// A collection of items in the repository. + /// + public IEnumerable Get(Expression> filter = null, + Func, IOrderedQueryable> orderBy = null, + string[] includeProperties = null) + { + IQueryable query = this.Items.AsQueryable(); + if (filter != null) + { + query = query.Where(filter); + } + + if (includeProperties != null) + { + foreach (var inc in includeProperties) + { + query = query.Include(inc); + } + } + + + return orderBy == null ? query : orderBy(query); + } + + /// + /// Gets the item identified by its Id. + /// + /// The unique identifier. + /// Item with the Id + public TEntity Get(int Id) + { + return this.Items.Find(Id); + } + + /// + /// Inserts a new item into the repository. + /// + /// The item to be inserted. + public void Insert(TEntity item) + { + this.Items.Add(item); + } + + /// + /// Deletes the item from the repository. + /// + /// The unique identifier of the item. + public void Delete(int Id) + { + Delete(new TEntity() { Id = Id}); //This is faster than getting the data from Db just to remove it + } + + /// + /// Deletes the item from the repository. + /// + /// The item to be deleted. + public void Delete(TEntity item) + { + if (this.Context.Entry(item).State == EntityState.Detached) + { + this.Items.Attach(item); + } + + //this.Context.Entry(item).State = EntityState.Deleted; //mark item to be deleted + this.Items.Remove(item); //THIS supports CASCADE deleting, the previous line will not work despite the fact that DB has "ON CASCADE DELETE" + } + + /// + /// Updates the item data in the repository. + /// + /// The new item data. + public void Update(TEntity item) + { + if (this.Context.Entry(item).State == EntityState.Detached) + { + this.Items.Attach(item); + } + + this.Context.Entry(item).State = EntityState.Modified; //mark item to be modified + } + + /// + /// Commits the changes that have been done to this repository since the last call of this method. + /// + /// It stores the local changes (in-memory) into database, + /// which may throw different exceptions regarding the concrete implementation. + /// For example, Entity Framework may throw DbEntityValidationException (if the model is not valid) or + ///DbUpdateConcurrencyException (two users has changed the same item). + public void Save() + { + this.Context.SaveChanges(); + } + } +} diff --git a/StudentEvaluatorCore/DAL/DbStudentEvaluationContext.cs b/StudentEvaluatorCore/DAL/DbStudentEvaluationContext.cs new file mode 100644 index 0000000..f65e6d6 --- /dev/null +++ b/StudentEvaluatorCore/DAL/DbStudentEvaluationContext.cs @@ -0,0 +1,50 @@ +using System.Data.Entity; +using Zcu.StudentEvaluator.Model; + +/* + Databaze bude vytvorena automaticky s nazvem Zcu.StudentEvaluator.DAL.DbStudentEvaluationContext + na Microsoft SQL Serveru: "(LocalDB)\v11.0" - toto je treba zadat do SQL Server Object Explorer + */ + +namespace Zcu.StudentEvaluator.DAL +{ + /// + /// Database Data context for student evaluations. + /// + /// + /// This is the main class that coordinates functionality for a given data model. + /// + public class DbStudentEvaluationContext : DbContext + { + /// + /// Gets or sets the repository of students. + /// + /// + /// The students repository. + /// + public DbSet Students { get; set; } + + /// + /// Gets or sets the repository of student evaluations. + /// + /// + /// The evaluations repository. + /// + public DbSet Evaluations { get; set; } + + /// + /// Gets or sets the repository of categories for the evaluation. + /// + /// + /// The categories repository. + /// + public DbSet Categories { get; set; } + + //protected override void OnModelCreating(DbModelBuilder modelBuilder) + //{ + // base.OnModelCreating(modelBuilder); + + // modelBuilder.Entity().Property(p => p.PersonalNumber).HasMaxLength(20); + //} + } +} diff --git a/StudentEvaluatorCore/DAL/DbStudentEvaluationUnitOfWork.cs b/StudentEvaluatorCore/DAL/DbStudentEvaluationUnitOfWork.cs new file mode 100644 index 0000000..550df7a --- /dev/null +++ b/StudentEvaluatorCore/DAL/DbStudentEvaluationUnitOfWork.cs @@ -0,0 +1,114 @@ +using System.Data.Entity; +using Zcu.StudentEvaluator.Migrations; +using Zcu.StudentEvaluator.Model; + +namespace Zcu.StudentEvaluator.DAL +{ + /// + /// This represents local in-memory unit of work that does not support any persistence + /// + public class DbStudentEvaluationUnitOfWork : IStudentEvaluationUnitOfWork + { + /// + /// The database context + /// + protected DbStudentEvaluationContext _context; + + /// + /// The repository of students in the context + /// + protected DbRepository _students; + + /// + /// The repository of categories in the context + /// + protected DbRepository _categories; + + /// + /// The repository of evaluations in the context + /// + protected DbRepository _evaluations; + + /// + /// Initializes a new instance of the class with the default context. + /// + public DbStudentEvaluationUnitOfWork() + { + //Update automatically the database to the latest version + Database.SetInitializer(new MigrateDatabaseToLatestVersion()); + + this._context = new DbStudentEvaluationContext(); + } + + /// + /// Initializes a new instance of the class. + /// + /// The data context. + public DbStudentEvaluationUnitOfWork(DbStudentEvaluationContext context) + { + this._context = context; + } + + /// + /// Gets the repository of students. + /// + /// + /// The students repository. + /// + public IRepository Students + { + get + { + if (this._students == null) + this._students = new DbRepository(this._context); + + return this._students; + } + } + + /// + /// Gets the repository of categories. + /// + /// + /// The categories repository. + /// + public IRepository Categories + { + get + { + if (this._categories == null) + this._categories = new DbRepository(this._context); + + return this._categories; + } + } + + /// + /// Gets the repository of evaluations. + /// + /// + /// The evaluations repository. + /// + public IRepository Evaluations + { + get + { + if (this._evaluations == null) + this._evaluations = new DbRepository(this._context); + + return this._evaluations; + } + } + + /// + /// Saves all changes done in unitOfWork. + /// + /// + /// Saves all changes into persistent stream. + /// + public void Save() + { + this._context.SaveChanges(); + } + } +} diff --git a/StudentEvaluatorCore/DAL/IRepository.cs b/StudentEvaluatorCore/DAL/IRepository.cs new file mode 100644 index 0000000..59109bb --- /dev/null +++ b/StudentEvaluatorCore/DAL/IRepository.cs @@ -0,0 +1,101 @@ +using System; +using System.Collections.Generic; +using System.Linq; +using System.Linq.Expressions; +using System.Runtime.CompilerServices; +using Zcu.StudentEvaluator.Model; + +/* + * Lecture Notes: vysvetlit rozdil mezi + * 1) Func filter, Func, IOrderedEnumerable> orderBy + * 2) Expression> filter, Func, IOrderedQueryable> orderBy + */ + +namespace Zcu.StudentEvaluator.DAL +{ + /// + /// This namespace contains classes and interfaces of Data Abstract Layer such as local (in-memory only), + /// or database repositories of the model classes. + /// + [CompilerGenerated] + internal class NamespaceDoc + { + //Trick to document a namespace + } + + + /// + /// Generic repository + /// + public interface IRepository where TEntity : class, IEntity, new() + { + /// + /// Gets the collection of all items. + /// + /// The filter specification - see remarks. + /// The order by specification - see remarks. + /// The include properties - see remarks. + /// + /// A collection of items in the repository. + /// + /// + /// Expression<Func<TEntity, bool>> filter means the caller will provide a lambda expression based on the TEntity + /// type, and this expression will return a Boolean value. For example, if the repository is instantiated for the Student entity type, + /// the code in the calling method might specify student => student.LastName == "Smith" for the filter parameter. + /// N.B. implementations should use AsQuerable() to filter in-memory only collections. + /// + /// Func<IQueryable<TEntity>, IOrderedQueryable<TEntity>> orderBy also means the caller will provide a lambda + /// expression for ordering the collection. But in this case, the input to the expression is an IQueryable object for the TEntity type. + /// The expression will return an ordered version of that IQueryable object. For example, if the repository is instantiated for the + /// Student entity type, the code in the calling method might specify q => q.OrderBy(s => s.LastName).ThenBy(s=> s.FirstName) + /// for the orderBy parameter. + /// + /// includeProperties is used when Entity Framework is used to specify which related objects should be retrieved from the database. + /// For example, if the repository is instantiated for the Student entity type, the code in the calling method might specify + /// new string[]{"Class"} to get valid Class object to which the student belongs. + /// + IEnumerable Get(Expression> filter = null, + Func, IOrderedQueryable> orderBy = null, + string[] includeProperties = null); + + /// + /// Gets the item identified by its Id. + /// + /// The unique identifier. + /// Item with the Id + TEntity Get(int Id); + + /// + /// Inserts a new item into the repository. + /// + /// The item to be inserted. + void Insert(TEntity item); + + /// + /// Deletes the item from the repository. + /// + /// The unique identifier of the item. + void Delete(int Id); + + /// + /// Deletes the item from the repository. + /// + /// The item to be deleted. + void Delete(TEntity item); + + /// + /// Updates the item data in the repository. + /// + /// The new item data. + void Update(TEntity item); + + /// + /// Commits the changes that have been done to this repository since the last call of this method. + /// + /// Implementations typically stores the local changes (in-memory) into a persistent stream, e.g., file or database, + /// which may throw different exceptions regarding the concrete implementation. + /// For example, Entity Framework may throw DbEntityValidationException (if the model is not valid) or + ///DbUpdateConcurrencyException (two users has changed the same item). + void Save(); + } +} diff --git a/StudentEvaluatorCore/DAL/IStudentEvaluationUnitOfWork.cs b/StudentEvaluatorCore/DAL/IStudentEvaluationUnitOfWork.cs new file mode 100644 index 0000000..5747911 --- /dev/null +++ b/StudentEvaluatorCore/DAL/IStudentEvaluationUnitOfWork.cs @@ -0,0 +1,42 @@ +using Zcu.StudentEvaluator.Model; + +namespace Zcu.StudentEvaluator.DAL +{ + /// + /// Represents ModelRepository, i.e., atomic part through which the rest of application access the model data. + /// + public interface IStudentEvaluationUnitOfWork + { + /// + /// Gets the repository of students. + /// + /// + /// The students repository. + /// + IRepository Students { get; } + + /// + /// Gets the repository of categories. + /// + /// + /// The categories repository. + /// + IRepository Categories { get; } + + /// + /// Gets the repository of evaluations. + /// + /// + /// The evaluations repository. + /// + IRepository Evaluations { get; } + + /// + /// Saves all changes done in unitOfWork. + /// + /// + /// Saves all changes into persistent stream. + /// + void Save(); + } +} diff --git a/StudentEvaluatorCore/DAL/LocalRepository.cs b/StudentEvaluatorCore/DAL/LocalRepository.cs new file mode 100644 index 0000000..1a71cb3 --- /dev/null +++ b/StudentEvaluatorCore/DAL/LocalRepository.cs @@ -0,0 +1,162 @@ +using System; +using System.Collections.Generic; +using System.Diagnostics.Contracts; +using System.Linq; +using System.Linq.Expressions; +using Zcu.StudentEvaluator.Model; + +namespace Zcu.StudentEvaluator.DAL +{ + /// + /// Generic repository of entities + /// + public class LocalRepository : IRepository + where TEntity: class, IEntity, new() + { + /// + /// The data context to be worked with + /// + protected LocalStudentEvaluationContext Context { get; private set; } + + /// + /// The collection containing the data for TEntity + /// + protected ICollection Items {get; private set; } + + /// + /// Gets or sets the next unique identifier. + /// + /// + /// The next unique identifier. + /// + protected int NextId { get; set; } + + /// + /// Initializes a new instance of the class. + /// + /// The context of this repository. + public LocalRepository(LocalStudentEvaluationContext context) + { + Contract.Requires(context != null); + + this.Context = context; + + //discover Items in the context + this.Items = DiscoverCollection(context); + + if (this.Items.Count != 0) + { + this.NextId = this.Items.Max(x => x.Id); + } + } + + /// + /// Discovers the collection. + /// + /// The context. + /// Instance of the collection of TEntity existing in the given context + private ICollection DiscoverCollection(LocalStudentEvaluationContext context) + { + Contract.Requires(context != null); + + //Reflection :-) + var getter = (from x in context.GetType().GetProperties() + where x.PropertyType.IsGenericType && + x.PropertyType.GenericTypeArguments.Contains(typeof(TEntity)) + select x.GetMethod).SingleOrDefault(); + + if (getter == null) + throw new ArgumentException("Public property returning a collection implementing ICollection<" + + typeof(TEntity).Name + "> could not been found in " + context.GetType().Name + "class.", "context"); + + return (ICollection)getter.Invoke(context, null); + } + + /// + /// Gets the collection of all items. + /// + /// The filter specification - see remarks. + /// The order by specification - see remarks. + /// The include properties - see remarks. + /// See IRepository + /// + /// A collection of items in the repository. + /// + public IEnumerable Get(Expression> filter = null, + Func, IOrderedQueryable> orderBy = null, + string[] includeProperties = null) + { + IQueryable query = this.Items.AsQueryable(); + if (filter != null) + { + query = query.Where(filter); + } + + return orderBy == null ? query : orderBy(query); + } + + /// + /// Gets the item identified by its Id. + /// + /// The unique identifier. + /// Item with the Id + public TEntity Get(int Id) + { + return this.Items.Where(x => x.Id == Id).Single(); + } + + /// + /// Inserts a new item into the repository. + /// + /// The item to be inserted. + public void Insert(TEntity item) + { + if (item.Id == 0) + item.Id = ++this.NextId; + else + this.NextId = Math.Max(this.NextId, item.Id); + + this.Items.Add(item); + } + + /// + /// Deletes the item from the repository. + /// + /// The unique identifier of the item. + public void Delete(int Id) + { + this.Items.Remove(this.Items.Where(x => x.Id == Id).Single()); + } + + /// + /// Deletes the item from the repository. + /// + /// The item to be deleted. + public void Delete(TEntity item) + { + this.Items.Remove(item); + } + + /// + /// Updates the item data in the repository. + /// + /// The new item data. + public void Update(TEntity item) + { + if (!this.Items.Contains(item)) + { + //replace existing data with the new one + Delete(item.Id); + this.Items.Add(item); + } + } + + /// + /// Commits the changes that have been done to this repository since the last call of this method. + /// + public void Save() + { + this.Context.SaveChanges(); + } + } +} diff --git a/StudentEvaluatorCore/DAL/LocalStudentEvaluationContext.cs b/StudentEvaluatorCore/DAL/LocalStudentEvaluationContext.cs new file mode 100644 index 0000000..60982c0 --- /dev/null +++ b/StudentEvaluatorCore/DAL/LocalStudentEvaluationContext.cs @@ -0,0 +1,57 @@ +using System.Collections.Generic; +using Zcu.StudentEvaluator.Model; + +namespace Zcu.StudentEvaluator.DAL +{ + /// + /// Data context for student evaluations. + /// + /// + /// This is the main class that coordinates functionality for a given data model. Later we will update it to EF database context. + /// + public class LocalStudentEvaluationContext + { + /// + /// Gets or sets the repository of students. + /// + /// + /// The students repository. + /// + public ICollection Students { get; set; } + + /// + /// Gets or sets the repository of student evaluations. + /// + /// + /// The evaluations repository. + /// + public ICollection Evaluations { get; set; } + + /// + /// Gets or sets the repository of categories for the evaluation. + /// + /// + /// The categories repository. + /// + public ICollection Categories { get; set; } + + /// + /// Initializes a new instance of the class. + /// + public LocalStudentEvaluationContext() + { + this.Students = new HashSet(); + this.Evaluations = new HashSet(); + this.Categories = new HashSet(); + } + + /// + /// Saves all changes made in this context to the underlying physical stuff. + /// + /// The number of objects written to the underlying physical stuff. + public virtual int SaveChanges() + { + return 0; + } + } +} diff --git a/StudentEvaluatorCore/DAL/LocalStudentEvaluationUnitOfWork.cs b/StudentEvaluatorCore/DAL/LocalStudentEvaluationUnitOfWork.cs new file mode 100644 index 0000000..056d227 --- /dev/null +++ b/StudentEvaluatorCore/DAL/LocalStudentEvaluationUnitOfWork.cs @@ -0,0 +1,109 @@ +using Zcu.StudentEvaluator.Model; + +namespace Zcu.StudentEvaluator.DAL +{ + /// + /// This represents local in-memory unit of work that does not support any persistence + /// + public class LocalStudentEvaluationUnitOfWork : IStudentEvaluationUnitOfWork + { + /// + /// The database context + /// + protected LocalStudentEvaluationContext _context; + + /// + /// The repository of students in the context + /// + protected LocalRepository _students; + + /// + /// The repository of categories in the context + /// + protected LocalRepository _categories; + + /// + /// The repository of evaluations in the context + /// + protected LocalRepository _evaluations; + + /// + /// Initializes a new instance of the class with the default context. + /// + public LocalStudentEvaluationUnitOfWork() + { + this._context = new LocalStudentEvaluationContext(); + } + + /// + /// Initializes a new instance of the class. + /// + /// The data context. + public LocalStudentEvaluationUnitOfWork(LocalStudentEvaluationContext context) + { + this._context = context; + } + + /// + /// Gets the repository of students. + /// + /// + /// The students repository. + /// + public IRepository Students + { + get + { + if (this._students == null) + this._students = new LocalRepository(this._context); + + return this._students; + } + } + + /// + /// Gets the repository of categories. + /// + /// + /// The categories repository. + /// + public IRepository Categories + { + get + { + if (this._categories == null) + this._categories = new LocalRepository(this._context); + + return this._categories; + } + } + + /// + /// Gets the repository of evaluations. + /// + /// + /// The evaluations repository. + /// + public IRepository Evaluations + { + get + { + if (this._evaluations == null) + this._evaluations = new LocalRepository(this._context); + + return this._evaluations; + } + } + + /// + /// Saves all changes done in unitOfWork. + /// + /// + /// Saves all changes into persistent stream. + /// + public void Save() + { + this._context.SaveChanges(); + } + } +} diff --git a/StudentEvaluatorCore/DAL/StudentEvaluationContextExtensions.cs b/StudentEvaluatorCore/DAL/StudentEvaluationContextExtensions.cs new file mode 100644 index 0000000..3fb4e42 --- /dev/null +++ b/StudentEvaluatorCore/DAL/StudentEvaluationContextExtensions.cs @@ -0,0 +1,111 @@ +using System; +using System.Collections.Generic; +using System.Diagnostics.Contracts; +using System.IO; +using Zcu.StudentEvaluator.DAL; +using Zcu.StudentEvaluator.Model; +using System.Linq; + +namespace Zcu.StudentEvaluator.DAL +{ + /// + /// Extension class + /// + /// Alternative solution would be Partial Class + public static class StudentEvaluationContextExtensions + { + /// + /// Populates the given work of unit with test data. + /// + /// The work of unit. + public static void PopulateWithData(this IStudentEvaluationUnitOfWork workOfUnit) + { + var categories = new List + { + new Category() { Name = "Design", MinPoints = 2m }, + new Category() { Name = "Implementation", MinPoints = 5m, MaxPoints = 10, }, + new Category() { Name = "CodeCulture" }, + new Category() { Name = "Documentation", MaxPoints = 2 }, + }; + + var students = new List + { + new Student() { PersonalNumber = "A12B0001P", FirstName = "Anna", Surname = "Aysle", }, + new Student() { PersonalNumber = "A12B0002P", FirstName = "Barbora", Surname = "Bílá", }, + new Student() { PersonalNumber = "A12B0003P", FirstName = "Cyril", Surname = "Cejn", }, + }; + + var evals = new List(); + for (int i = 0, idx = 0; i < students.Count; i++) + { + for (int j = 0; j < categories.Count; j++, idx++) + { + var eval = new Evaluation() + { + Category = categories[j], + Student = students[i], + }; + + evals.Add(eval); + } + } + + evals[0].Points = 5m; + evals[1].Points = 9m; + + evals.ForEach(x => + { + if (x.Category.Evaluations == null) + x.Category.Evaluations = new List(); + x.Category.Evaluations.Add(x); + + if (x.Student.Evaluations == null) + x.Student.Evaluations = new List(); + + x.Student.Evaluations.Add(x); + }); + + students.ForEach(x => workOfUnit.Students.Insert(x)); + categories.ForEach(x => workOfUnit.Categories.Insert(x)); + evals.ForEach(x => workOfUnit.Evaluations.Insert(x)); + + workOfUnit.Save(); + } + + /// + /// Dumps the data in the repository using the specified output. + /// + /// The database context. + /// The user message. + /// The output, e.g.., Console.Out. + public static void DumpData(this IStudentEvaluationUnitOfWork context, string userMessage = null, TextWriter output = null) + { + Contract.Requires(context != null); + + if (output == null) + output = Console.Out; + + output.WriteLine("================================="); + + if (userMessage != null) + { + output.Write("====> "); + output.Write(userMessage); + output.WriteLine(" <===="); + } + + var students = context.Students.Get(); + + output.WriteLine("Number of students: " + students.Count()); + output.WriteLine("---------------------------------"); + foreach (var st in students) + { + output.WriteLine("{0} {1}, {2} - evaluations: {3}", + st.Surname, st.FirstName, st.PersonalNumber, st.Evaluations.Count); + } + + output.WriteLine("================================="); + output.WriteLine(); + } + } +} diff --git a/StudentEvaluatorCore/DAL/XmlStudentEvaluationContext.cs b/StudentEvaluatorCore/DAL/XmlStudentEvaluationContext.cs new file mode 100644 index 0000000..767499e --- /dev/null +++ b/StudentEvaluatorCore/DAL/XmlStudentEvaluationContext.cs @@ -0,0 +1,178 @@ +using System; +using System.Collections.Generic; +using System.Configuration; +using System.Linq; +using System.Text; +using System.Xml; +using System.Xml.Serialization; +using Zcu.StudentEvaluator.Model; + +namespace Zcu.StudentEvaluator.DAL +{ + /// + /// Data context for student evaluations. + /// + /// + /// This is the main class that coordinates functionality for a given data model with Xml persistency. + /// + public class XmlStudentEvaluationContext : LocalStudentEvaluationContext + { + /// + /// Gets the filename of Xml file used to store the data. + /// + /// + /// The XML connection filename. + /// + protected string XmlConnectionFilename { get; private set; } + + /// + /// Initializes a new instance of the class. + /// + /// XmlConnectionFilename containing the data is automatically retrieved from application Properties + public XmlStudentEvaluationContext() : base() + { + try + { + this.XmlConnectionFilename = ( + from ConnectionStringSettings x in ConfigurationManager.ConnectionStrings + where x.Name == "XmlConnectionFilename" + select x.ConnectionString + ).SingleOrDefault(); + } + catch (Exception) + { + + } + + if (this.XmlConnectionFilename == null) + { + this.XmlConnectionFilename = "localData.xml"; + } + + Load(); + } + + /// + /// Initializes a new instance of the class. + /// + /// PathName to XML containing the data. + public XmlStudentEvaluationContext(string xmlConnectionFilename) : base() + { + this.XmlConnectionFilename = xmlConnectionFilename; + + Load(); + } + + /// + /// Loads the data from the underlying Xml file into the local in-memory unitOfWork. + /// + protected virtual void Load() + { + if (!System.IO.File.Exists(this.XmlConnectionFilename)) + return; //nothing to load + + using (XmlReader xmlReader = XmlReader.Create(this.XmlConnectionFilename)) + { + xmlReader.ReadStartElement(); + + XmlAttributeOverrides xmlOvers = CreateXmlAttributeOverrides(); + + var serStudent = new XmlSerializer(this.Students.GetType(), xmlOvers); + this.Students = (ICollection)serStudent.Deserialize(xmlReader); + + var serCategory = new XmlSerializer(this.Categories.GetType(), xmlOvers); + this.Categories = (ICollection)serCategory.Deserialize(xmlReader); + + var serEvals = new XmlSerializer(this.Evaluations.GetType(), xmlOvers); + this.Evaluations = (ICollection)serEvals.Deserialize(xmlReader); + + xmlReader.ReadEndElement(); + xmlReader.Close(); + } + + //create connections + foreach (var ev in this.Evaluations) + { + if (ev.Category != null) + { + ev.Category = this.Categories.Where(x => x.Id == ev.Category.Id).Single(); + ev.Category.Evaluations.Add(ev); + } + + if (ev.Student != null) + { + ev.Student = this.Students.Where(x => x.Id == ev.Student.Id).Single(); + ev.Student.Evaluations.Add(ev); + } + } + } + + /// + /// Saves all changes made in this context to the underlying physical stuff. + /// + /// The number of objects written to the underlying Xml. + public override int SaveChanges() + { + using (XmlWriter xmlWriter = XmlWriter.Create(this.XmlConnectionFilename, new XmlWriterSettings() + { + Encoding = Encoding.UTF8, + Indent = true, + })) + { + xmlWriter.WriteStartElement("Root"); + + XmlAttributeOverrides xmlOvers = CreateXmlAttributeOverrides(); + + var serStudent = new XmlSerializer(this.Students.GetType(), xmlOvers); + serStudent.Serialize(xmlWriter, this.Students); + + var serCategory = new XmlSerializer(this.Categories.GetType(), xmlOvers); + serCategory.Serialize(xmlWriter, this.Categories); + + var serEvals = new XmlSerializer(this.Evaluations.GetType(), xmlOvers); + serEvals.Serialize(xmlWriter, this.Evaluations); + + xmlWriter.WriteEndElement(); + xmlWriter.Close(); + } + + return this.Students.Count + this.Categories.Count + this.Evaluations.Count; + } + + /// + /// Creates the XML attribute overrides to be used with XmlSerializer. + /// + /// Created overrides + private XmlAttributeOverrides CreateXmlAttributeOverrides() + { + XmlAttributeOverrides xmlOvers = new XmlAttributeOverrides(); + var xmlEvaluationsAttr = new XmlAttributes(); + xmlEvaluationsAttr.XmlIgnore = true; + + xmlOvers.Add(typeof(Student), "Evaluations", xmlEvaluationsAttr); + xmlOvers.Add(typeof(Category), "Evaluations", xmlEvaluationsAttr); + + /*var xmlStudents = new XmlAttributes(); + xmlStudents.XmlArray = new XmlArrayAttribute("Students"); + xmlOvers.Add(this.Students.GetType(), xmlStudents); + + var xmlCategories = new XmlAttributes(); + xmlCategories.XmlArray = new XmlArrayAttribute() + { + ElementName = "Categories" + }; + + xmlOvers.Add(this.Categories.GetType(), xmlCategories); + + var xmlEvaluations = new XmlAttributes(); + xmlEvaluations.XmlArray = new XmlArrayAttribute() + { + ElementName = "Evaluations" + }; + + xmlOvers.Add(this.Evaluations.GetType(), xmlEvaluations); + */ + return xmlOvers; + } + } +} diff --git a/StudentEvaluatorCore/DialogService/DialogService.cs b/StudentEvaluatorCore/DialogService/DialogService.cs new file mode 100644 index 0000000..99af8d0 --- /dev/null +++ b/StudentEvaluatorCore/DialogService/DialogService.cs @@ -0,0 +1,159 @@ +using System; +using System.Collections.Generic; +using System.Diagnostics.CodeAnalysis; +using System.Diagnostics.Contracts; +using System.Linq; + +namespace Zcu.StudentEvaluator.DialogService +{ + /// + /// Provides support for dialog between ViewModels and Views. + /// + public sealed class DialogService : IDialogService + { + private static IDialogService _defaultService = null; + + /// + /// Gets or sets the default. + /// + /// + /// The default. + /// + public static IDialogService Default + { + get + { + if (_defaultService == null) + _defaultService = new DialogService(); + + return _defaultService; + } + } + + private DialogService () + { + //private ctor to prevent construction + } + + + private class RegistryEntryBase + { + public DialogConstants viewId; + } + + private class RegistryEntry : RegistryEntryBase + { + public Func instanceCreator; + } + + private Dictionary> _registry = new Dictionary>(); + + /// + /// Registers the view. + /// + /// The type of the view interface, e.g., IMainView. + /// The delegate to a function that creates a new instance of the concrete implementation of TviewInterface, + /// e.g., MainView, usually specified as a lambda expression: () => new MainView(param1, param2) + /// The requested view unique identifier. + /// If an interface is implemented by one concrete class only, viewId can be ignored, otherwise it is important. + /// The assigned view unique identifier (that may be passed to Get method) + public DialogConstants Register(Func instanceCreator, DialogConstants viewId = DialogConstants.AutoResolve) where TviewInterface : class + { + var type = typeof(TviewInterface); + + List list; + if (!_registry.TryGetValue(type, out list)) + _registry.Add(type, list = new List()); + + Contract.Assume(list != null); + if (viewId != DialogConstants.AutoResolve) + { + if (list.Find(x => x.viewId == viewId) != null) + throw new ArgumentException("Interface is already registered for view with id=" + viewId); + } + else + { + //automatically assign a new Constant + Contract.Assume(Contract.ForAll(list, x => x != null)); + + int maxId = Math.Max(Enum.GetValues(typeof(DialogConstants)).Cast().Max(), list.Max(x => (int)x.viewId)); + viewId = (DialogConstants)(maxId + 1); + } + + var entry = new RegistryEntry() + { + viewId = viewId, + instanceCreator = instanceCreator, + }; + + list.Add(entry); + return viewId; + } + + /// + /// Registers the view. + /// + /// The type of the view interface, e.g., IMainView. + /// The type of the view implementation, e.g., MainView (implements IMainView). + /// The requested view unique identifier. + /// + /// The assigned view unique identifier (that may be passed to Get method) + /// + /// + /// When the instance is required, non-parametric constructor is used. + /// If an interface is implemented by one concrete class only, viewId can be ignored, otherwise it is important. + /// + public DialogConstants Register(DialogConstants viewId = DialogConstants.AutoResolve) + where TviewInterface : class + where TviewImplementation : TviewInterface, new() + { + return Register(() => new TviewImplementation(), viewId); + } + + /// + /// Registers the view. + /// + /// The type of the view interface, e.g., IMainView. + /// The type of the view implementation, e.g., MainView (implements IMainView). + /// The existing instance to the sigleton. + /// The requested view unique identifier. + /// + /// The assigned view unique identifier (that may be passed to Get method) + /// + /// + /// When the instance is required, the same instance (instance) is always returned, i.e., this method register + /// singleton view (has shared instance). If an interface is implemented by one concrete class only, viewId can be ignored, otherwise it is important. + /// + public DialogConstants RegisterSingleton(TviewImplementation instance, DialogConstants viewId = DialogConstants.AutoResolve) + where TviewInterface : class + where TviewImplementation : TviewInterface + { + return Register(() => instance, viewId); + } + + + /// + /// Gets the view identified by its type and optionally by its unique identifier. + /// + /// The type of the view interface. + /// The view unique identifier from Register methods. + /// null, if the view does not exist, otherwise instance of the registered view of the specified type. + public TviewInterface Get(DialogConstants viewId = DialogConstants.AutoResolve) + where TviewInterface : class + { + List list; + if (!_registry.TryGetValue(typeof(TviewInterface), out list)) + return null; + + RegistryEntry entry = null; + if (viewId != DialogConstants.AutoResolve) + entry = (RegistryEntry < TviewInterface >) list.Where(x => x.viewId == viewId).SingleOrDefault(); + else if (list.Count > 1) + throw new ArgumentException("Cannot automatically resolve the concrete implementation for " + typeof(TviewInterface).Name); + else if (list.Count > 0) + entry = (RegistryEntry < TviewInterface >) list[0]; + + return entry != null ? entry.instanceCreator() : null; + } + } +} diff --git a/StudentEvaluatorCore/DialogService/IDialogService.cs b/StudentEvaluatorCore/DialogService/IDialogService.cs new file mode 100644 index 0000000..33d7b7d --- /dev/null +++ b/StudentEvaluatorCore/DialogService/IDialogService.cs @@ -0,0 +1,105 @@ +using System; +using System.Runtime.CompilerServices; + +namespace Zcu.StudentEvaluator.DialogService +{ + /// + /// This namespace contains classes and interfaces to support binding of concrete implementation of Views with unique View interfaces. + /// + [CompilerGenerated] + internal class NamespaceDoc + { + //Trick to document a namespace + } + + /// + /// Identifiers of well-known Views + /// + public enum DialogConstants : int + { + /// + /// The view should be automatically resolved from the specified interface type + /// + AutoResolve, + + /// + /// The view used to notify the user about errors, warnings, messages, ... + /// + NotificationView, + + /// + /// The view allowing confirmation/rejection of some action by the user + /// + ConfirmationView, + + /// + /// The view for editing the data of the given student viewmodel + /// + EditStudentView, + } + + /// + /// Provides support for dialog between ViewModels and Views. + /// + /// Application uses the concrete class of IDialogService to register concrete View implementations of various View interfaces. + /// ViewModels access Views objects via their registered interfaces. This actually splits Views from ViewModels. + /// However, the project grows (due to a heavy use of interfaces) + public interface IDialogService + { + /// + /// Registers the view. + /// + /// The type of the view interface, e.g., IMainView. + /// The delegate to a function that creates a new instance of the concrete implementation of TviewInterface, + /// e.g., MainView, usually specified as a lambda expression: () => new MainView(param1, param2) + /// The requested view unique identifier. + /// If an interface is implemented by one concrete class only, viewId can be ignored, otherwise it is important. + /// The assigned view unique identifier (that may be passed to Get method) + DialogConstants Register(Func instanceCreator, DialogConstants viewId = DialogConstants.AutoResolve) + where TviewInterface : class; + + /// + /// Registers the view. + /// + /// The type of the view interface, e.g., IMainView. + /// The type of the view implementation, e.g., MainView (implements IMainView). + /// The requested view unique identifier. + /// + /// The assigned view unique identifier (that may be passed to Get method) + /// + /// + /// When the instance is required, non-parametric constructor is used. + /// If an interface is implemented by one concrete class only, viewId can be ignored, otherwise it is important. + /// + DialogConstants Register(DialogConstants viewId = DialogConstants.AutoResolve) + where TviewInterface : class + where TviewImplementation : TviewInterface, new(); + + /// + /// Registers the view. + /// + /// The type of the view interface, e.g., IMainView. + /// The type of the view implementation, e.g., MainView (implements IMainView). + /// The existing instance to the sigleton. + /// The requested view unique identifier. + /// + /// The assigned view unique identifier (that may be passed to Get method) + /// + /// + /// When the instance is required, the same instance (instance) is always returned, i.e., this method register + /// singleton view (has shared instance). If an interface is implemented by one concrete class only, viewId can be ignored, otherwise it is important. + /// + DialogConstants RegisterSingleton(TviewImplementation instance, DialogConstants viewId = DialogConstants.AutoResolve) + where TviewInterface : class + where TviewImplementation : TviewInterface; + + /// + /// Gets the view identified by its type and optionally by its unique identifier. + /// + /// The type of the view interface. + /// The view unique identifier from Register methods. + /// null, if the view does not exist, otherwise instance of the registered view of the specified type. + TviewInterface Get(DialogConstants viewId = DialogConstants.AutoResolve) + where TviewInterface : class; + } +} diff --git a/StudentEvaluatorCore/Migrations/201310070645097_InitialCreate.Designer.cs b/StudentEvaluatorCore/Migrations/201310070645097_InitialCreate.Designer.cs new file mode 100644 index 0000000..2470b4c --- /dev/null +++ b/StudentEvaluatorCore/Migrations/201310070645097_InitialCreate.Designer.cs @@ -0,0 +1,26 @@ +// +namespace StudentEvaluatorConsoleApp.Migrations +{ + using System.Data.Entity.Migrations.Infrastructure; + using System.Resources; + + public sealed partial class InitialCreate : IMigrationMetadata + { + private readonly ResourceManager Resources = new ResourceManager(typeof(InitialCreate)); + + string IMigrationMetadata.Id + { + get { return "201310070645097_InitialCreate"; } + } + + string IMigrationMetadata.Source + { + get { return null; } + } + + string IMigrationMetadata.Target + { + get { return Resources.GetString("Target"); } + } + } +} diff --git a/StudentEvaluatorCore/Migrations/201310070645097_InitialCreate.cs b/StudentEvaluatorCore/Migrations/201310070645097_InitialCreate.cs new file mode 100644 index 0000000..7a072f2 --- /dev/null +++ b/StudentEvaluatorCore/Migrations/201310070645097_InitialCreate.cs @@ -0,0 +1,70 @@ +namespace StudentEvaluatorConsoleApp.Migrations +{ + using System.Data.Entity.Migrations; + + /// + /// Creates the version 1.0 of the database + /// + public partial class InitialCreate : DbMigration + { + /// + /// Operations to be performed during the upgrade process. + /// + public override void Up() + { + CreateTable( + "dbo.Students", + c => new + { + Id = c.Int(nullable: false, identity: true), + PersonalNumber = c.String(nullable: false, maxLength: 10), + FirstName = c.String(nullable: false, maxLength: 25), + Surname = c.String(nullable: false, maxLength: 25), + }) + .PrimaryKey(t => t.Id) + .Index(t => t.PersonalNumber, true); //PersonalNumber is unique + + CreateTable( + "dbo.Evaluations", + c => new + { + Id = c.Int(nullable: false, identity: true), + Points = c.Decimal(precision: 18, scale: 2), + Reason = c.String(), + Category_Id = c.Int(), + Student_Id = c.Int(), + }) + .PrimaryKey(t => t.Id) + .ForeignKey("dbo.Categories", t => t.Category_Id) + .ForeignKey("dbo.Students", t => t.Student_Id) + .Index(t => t.Category_Id) + .Index(t => t.Student_Id); + + CreateTable( + "dbo.Categories", + c => new + { + Id = c.Int(nullable: false, identity: true), + Name = c.String(), + MinPoints = c.Decimal(precision: 18, scale: 2), + MaxPoints = c.Decimal(precision: 18, scale: 2), + }) + .PrimaryKey(t => t.Id); + + } + + /// + /// Operations to be performed during the downgrade process. + /// + public override void Down() + { + DropIndex("dbo.Evaluations", new[] { "Student_Id" }); + DropIndex("dbo.Evaluations", new[] { "Category_Id" }); + DropForeignKey("dbo.Evaluations", "Student_Id", "dbo.Students"); + DropForeignKey("dbo.Evaluations", "Category_Id", "dbo.Categories"); + DropTable("dbo.Categories"); + DropTable("dbo.Evaluations"); + DropTable("dbo.Students"); + } + } +} diff --git a/StudentEvaluatorCore/Migrations/201310070645097_InitialCreate.resx b/StudentEvaluatorCore/Migrations/201310070645097_InitialCreate.resx new file mode 100644 index 0000000..722f337 --- /dev/null +++ b/StudentEvaluatorCore/Migrations/201310070645097_InitialCreate.resx @@ -0,0 +1,123 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + text/microsoft-resx + + + 2.0 + + + System.Resources.ResXResourceReader, System.Windows.Forms, Version=4.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089 + + + System.Resources.ResXResourceWriter, System.Windows.Forms, Version=4.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089 + + + H4sIAAAAAAAEAN1aS2/cNhC+F+h/EHRqC2RlOymQBusErh+FkfgBy8mhF4OWuGuiFKmKlLH723roT+pf6FBvkpJWkncdJzctH/PiN8MZzv73z7/zD6uIOo84EYSzQ3d/tuc6mAU8JGx56KZy8eqt++H9jz/MT8No5Xwp171W62AnE4fug5TxO88TwQOOkJhFJEi44As5C3jkoZB7B3t7v3n7+x4GEi7Qcpz5TcokiXD2A34ecxbgWKaIXvAQU1GMw4yfUXUuUYRFjAJ86P4ZpDNfpiFm8vQR0RRJnsxOjj65zhElCOTxMV24Tvzm3WeBfZlwtvRjJAmit+sYw/wCUYEL6d/Fb4YqsHegFPAQY1wCOc4mGcCtVAPlTsEIcq3EyhQE0XO9motg2Ue81gZg6DrhMU7k+gYviq3noet4+j7P3Fhta+xR3OGLydcHrnOZUoruKa6MBFb0wb74D8xwgiQOr5GUOGFqL86kt7gaPK4BMpwheplG9zgp+cG5AMBc54yscPgJs6V8qHheoFU5sg8o+8wIwBH2yCTFLSL2sz8jiZDqcyTng1+fytlPE/YsfC/RI1lmiDQkKNwDJoTr3GCafz6QOPeRWT1/VwLPOUt4dMOptrucvbtFyRLDolveucTnaRIYMs69Gui98K/pfVcewAmTouRzggMSIeo61wl8FVH3rev4AVJcNRHy8++nfoORULFoFMrgswdmrWw7UXYMRlnyZN0KsXLyTsNiDbK2eQtmrYvagNYnZoXwbThCybzHEUolJjlCZdPvyA0mBOEJMDWYXhC2W/cDEbfMYHI4n+RrJpJ7HXIIlo+E4AHJNhpg1uTSdT5loTNEyBw8TWUBLymVJKYkAGEO3b3ZbN8y6QbyVbypyTduIoPBL6YZGgr326ElynSJ2RdyJgi5iXaLAao4uMm8nQbI4QGZvUQEYkZhhJN7PXeHTWoJXtnGUNt9LPX4DYiucWfKaumsk9D8wqLStOYGQgWKCG6jUyPToNKwk0XKcNrG0j7nNi+DgW5UqaNr4o0kV6KmQU5TwryEdP0H2KbtWrYts8mthjrWMDWGulKDWo3cwRYpg2vlPXVB7OUVcVk5ex2l8/wCxTHcsY1Suhhx/LyOPn7lj69go5yGF4iWQraStuIEGQNaYmMWWIOkWWF2giS6R+rWPw4ja9n4WFEyNkKGfYKlz5Yb1He+qet5YdYBrtqwZ6BrBCsytbENAHtn9r6BKEpa0rFjTtOIdaV0fbvNirtJyZwbTrVRSDcJNoaH06pK4yalatCmM/cME5sH6lknaniaiZBB+OkPtRMg1FXfDkJR3+YdAalIbDUAFWPDqZQFapNKOfZijrpxD27lpNsLuEHn3L11N6ds+/RYd24UWU0yjeERtOp6SqNVDz8/ZvRruhU4Rv42AiH6xuFOr9KQjpeYtqTPNtogKFUk2zCl7FbJMFG8Il+aKN5oqSB/CElWk50LVYdXrw4DlTZzttF4actpR90c9bYnYaU7dZ14FCWVLQClO0l/UTDp1ngzSqwE31xSxbQq0TcS+nmRXG9umFnZdr5EvVDxRxKqTNtfC4mjmVow8/+mx5RkCCsXXCBGFljIW/4XhrILioG3RrdtQifMEyKkL7QdRir/6nvvHNkQau+EsUeUBA8osfpeT210tRJW7aWn9bGmkf0mWkC7OXXtdTjcafenPJufIrT6eSwp7dZrGGQclWZQHEjkG+iK7AQZbc466eSsJse2UWY1OZ7GYMetg230CZqPwRNf3AF7OFHQQBRueSETuOetyuQ6ISwgMaKmDnamMwTTyrYVSXPmBMeYKbDaOg7htqEUqKgbTrbJDLtpoGi59diWxotAQvdz49cGQn+mv3UctPeR7CfkQe2jvu5RnhZDcLvncNR58Op8u+/uLW1oLbVx6WubdPSe1r2dpzYm3a2er9KZamtDWT2zTk9sL3tfZMvJQJ7WEXgudUf0k+yyEny38W9NiB6CLGsSqlZmONC8tlpzzha8DCCGROUSM9XAEoXg0keJJAsUSJgOsBDZv0W+gObKMFA+hefsKpVxKkFlHN1TDTkqCPXxz5pmuszzqzgD7jZUADEJqICv2O8poWEl91lLWdRBQkW3ItNUZylVxrlcV5QuORtIqDBfFZRvcRRTICaumI8ecbdsm22oW2x+QtAyQZEoaNT74SfAL4xW7/8Hq3QoxW8sAAA= + + \ No newline at end of file diff --git a/StudentEvaluatorCore/Migrations/201310070652287_CategoryNameIsUnique.Designer.cs b/StudentEvaluatorCore/Migrations/201310070652287_CategoryNameIsUnique.Designer.cs new file mode 100644 index 0000000..5c650e8 --- /dev/null +++ b/StudentEvaluatorCore/Migrations/201310070652287_CategoryNameIsUnique.Designer.cs @@ -0,0 +1,26 @@ +// +namespace StudentEvaluatorConsoleApp.Migrations +{ + using System.Data.Entity.Migrations.Infrastructure; + using System.Resources; + + public sealed partial class CategoryNameIsUnique : IMigrationMetadata + { + private readonly ResourceManager Resources = new ResourceManager(typeof(CategoryNameIsUnique)); + + string IMigrationMetadata.Id + { + get { return "201310070652287_CategoryNameIsUnique"; } + } + + string IMigrationMetadata.Source + { + get { return null; } + } + + string IMigrationMetadata.Target + { + get { return Resources.GetString("Target"); } + } + } +} diff --git a/StudentEvaluatorCore/Migrations/201310070652287_CategoryNameIsUnique.cs b/StudentEvaluatorCore/Migrations/201310070652287_CategoryNameIsUnique.cs new file mode 100644 index 0000000..cd870ef --- /dev/null +++ b/StudentEvaluatorCore/Migrations/201310070652287_CategoryNameIsUnique.cs @@ -0,0 +1,28 @@ +namespace StudentEvaluatorConsoleApp.Migrations +{ + using System.Data.Entity.Migrations; + + /// + /// Database upgrade to ensure that the name of a cateogry is unique + /// + public partial class CategoryNameIsUnique : DbMigration + { + /// + /// Operations to be performed during the upgrade process. + /// + public override void Up() + { + AlterColumn("dbo.Categories", "Name", c => c.String(nullable: false, maxLength: 50)); + CreateIndex("dbo.Categories", "Name", true); + } + + /// + /// Operations to be performed during the downgrade process. + /// + public override void Down() + { + DropIndex("dbo.Categories", "Name"); + AlterColumn("dbo.Categories", "Name", c => c.String()); + } + } +} diff --git a/StudentEvaluatorCore/Migrations/201310070652287_CategoryNameIsUnique.resx b/StudentEvaluatorCore/Migrations/201310070652287_CategoryNameIsUnique.resx new file mode 100644 index 0000000..3ed0432 --- /dev/null +++ b/StudentEvaluatorCore/Migrations/201310070652287_CategoryNameIsUnique.resx @@ -0,0 +1,123 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + text/microsoft-resx + + + 2.0 + + + System.Resources.ResXResourceReader, System.Windows.Forms, Version=4.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089 + + + System.Resources.ResXResourceWriter, System.Windows.Forms, Version=4.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089 + + + H4sIAAAAAAAEAN1aS2/cNhC+F+h/EHRqC2RlOwmQGusErh+F0fgBy8mhF4OWuGuiFKmKlLH723roT+pf6FBvkpJWkncTJzctH/PiN8MZzv73z7/zD6uIOk84EYSzI3d/tuc6mAU8JGx55KZy8eqd++H9jz/Mz8Jo5Xwu171W62AnE0fuo5TxoeeJ4BFHSMwiEiRc8IWcBTzyUMi9g729X739fQ8DCRdoOc78NmWSRDj7AT9POAtwLFNEL3mIqSjGYcbPqDpXKMIiRgE+cv8M0pkv0xAzefaEaIokT2anxx9d55gSBPL4mC5cJ35z+ElgXyacLf0YSYLo3TrGML9AVOBC+sP4zVAF9g6UAh5ijEsgx9kkA7iVaqDcGRhBrpVYmYIgeq5XcxEs+wOvtQEYukl4jBO5vsWLYutF6Dqevs8zN1bbGnsUd/hi8vWB61yllKIHiisjgRV9sC/+HTOcIInDGyQlTpjaizPpLa4GjxuADGeIXqXRA05KfnAuADDXOScrHH7EbCkfK56XaFWO7APKPjECcIQ9Mklxi4j97M9JIqT6HMn54O1zOftpwr4I3yv0RJYZIg0JCveACeE6t5jmn48kzn1kVs/fl8BzzhMe3XKq7S5n7+9QssSw6I53LvF5mgSGjHOvBnov/Gt635UHcMKkKPmc4oBEiLrOTQJfRdR95zp+gBRXTYT8/Pup32IkVCwahTL47IFZK9tOlJ2AUZY8WbdCrJy817BYg6xt3oJZ66I2oPWJWSF8G45QMu9xhFKJSY5Q2fQ7coMJQfjts8P/JWG7dT+QdssMJofzSb5mIrnXIYdg+VgIHpBsowFmTS5d5zMWOkOEzMHTVBbwklJJYkoCEObI3ZvN9i2TbiBfxZuafOMmMhj8YpqhoXC/HVqiTJeYfSFngpCbaLcYoIqDm8zbaYAcHpDZS0QgZhRGOH3Qc3fYpJbglW0Mtd3HUo/fgOgad6asls46Cc0vLCpNa24gVKCI4DY6NTINKg07WaQMp20s7XNu8zIY6EaVOrom3khyJWoa5DQlzEtI13+AbdquZdsym9xqqGMNU2OoKzWo1cgdbJEyuFbeUxfEXl4Rl5Wz11E6zy9RHMMd2yilixHHz+vok1f++Ao2yml4gWgpZCtpK06QMaAlNmaBNUiaFWanSKIHpK7xkzCylo2PFSVjI2TYJ1j6bLlBfeebup4XZh3gqg17DrpGsCJTG9sAsHdm7xuIoqQlHTvhNI1YV0rXt9usuJuUzLnhVBuFdJNgY3g4rao0blKqBm06c88wsXmgnnWihqeZCBmEn/5QOwFCXfXtIBT1bd4RkIrEVgNQMTacSlmgNqmUYy/mqBv34FZOur2AG3TO3Vt3c8q2T49150aR1STTGB5Bq66nNFr18JfHjH5NtwLHyN9GIETfONzpVRrS8RLTlvTZRhsEpYpkG6aU3SoZJopX5EsTxRstFeQPIclqsguh6vDqGWGg0mbONhovbTntqJuj3vYsrHSnrhOPoqSyBaB0J+kvCibdGm9GiZXgm0uqmFYl+kZCPy+S680NMyvbzpeoFyr+REKVaftrIXE0Uwtm/t/0hJIMYeWCS8TIAgt5x//CUHZBMfDO6LZN6IR5QoT0hbbDSOVffe+dI98i2zth7AklwSNKrL7XcxtdrYRVe+l5faxpZL+JFtBuTl17HQ532v0pz+anCK1+HktKu/UaBhlHpRkUBxL5BroiO0HGRmd9Oz4KWP2ObQPO6nc8j8GOuwjbaBk034UnPr4DDHGiUIIoXPhCJnDlW0XKTUJYQGJETR3spGcIvJVtK5LmzCmOMVO4tXUcwm1DVVBRN/xtkxl200vR0uyx3Y0XgYTul8evDYT+pH/rOGhvKdmvyYM6SX2NpDxDhuD2wOGo8+DV+Yzf3Wba0GVq49LXQeloQ617m1BtTLq7Pl+lSdXWkbLaZ52e2F4Bv8juk4E8rTnwpdQd0VqyK0zw3cYfNyF6CLKsSaiymeFA89pqzQVb8DKAGBKVS8xUA0sUgksfJ5IsUCBhOsBCZH8c+QyaK8NAJRVesOtUxqkElXH0QDXkqCDUxz/rn+kyz6/jDLjbUAHEJKACvma/pYSGldznLRVSBwkV3YqkU52lVMnncl1RuuJsIKHCfFVQvsNRTIGYuGY+esLdsm22oW6x+SlBywRFoqBR74efAL8wWr3/H21iiWN6LAAA + + \ No newline at end of file diff --git a/StudentEvaluatorCore/Migrations/201310070731399_EvaluationInitialConstraints.Designer.cs b/StudentEvaluatorCore/Migrations/201310070731399_EvaluationInitialConstraints.Designer.cs new file mode 100644 index 0000000..589af5a --- /dev/null +++ b/StudentEvaluatorCore/Migrations/201310070731399_EvaluationInitialConstraints.Designer.cs @@ -0,0 +1,26 @@ +// +namespace StudentEvaluatorConsoleApp.Migrations +{ + using System.Data.Entity.Migrations.Infrastructure; + using System.Resources; + + public sealed partial class EvaluationInitialConstraints : IMigrationMetadata + { + private readonly ResourceManager Resources = new ResourceManager(typeof(EvaluationInitialConstraints)); + + string IMigrationMetadata.Id + { + get { return "201310070731399_EvaluationInitialConstraints"; } + } + + string IMigrationMetadata.Source + { + get { return null; } + } + + string IMigrationMetadata.Target + { + get { return Resources.GetString("Target"); } + } + } +} diff --git a/StudentEvaluatorCore/Migrations/201310070731399_EvaluationInitialConstraints.cs b/StudentEvaluatorCore/Migrations/201310070731399_EvaluationInitialConstraints.cs new file mode 100644 index 0000000..b2a0db5 --- /dev/null +++ b/StudentEvaluatorCore/Migrations/201310070731399_EvaluationInitialConstraints.cs @@ -0,0 +1,44 @@ +namespace StudentEvaluatorConsoleApp.Migrations +{ + using System.Data.Entity.Migrations; + + /// + /// Database upgrade to add CASCADE DELETE + /// + public partial class EvaluationInitialConstraints : DbMigration + { + /// + /// Operations to be performed during the upgrade process. + /// + public override void Up() + { + DropForeignKey("dbo.Evaluations", "Category_Id", "dbo.Categories"); + DropForeignKey("dbo.Evaluations", "Student_Id", "dbo.Students"); + DropIndex("dbo.Evaluations", new[] { "Category_Id" }); + DropIndex("dbo.Evaluations", new[] { "Student_Id" }); + AlterColumn("dbo.Evaluations", "Category_Id", c => c.Int(nullable: false)); + AlterColumn("dbo.Evaluations", "Student_Id", c => c.Int(nullable: false)); + AddForeignKey("dbo.Evaluations", "Category_Id", "dbo.Categories", "Id", cascadeDelete: true); + AddForeignKey("dbo.Evaluations", "Student_Id", "dbo.Students", "Id", cascadeDelete: true); + CreateIndex("dbo.Evaluations", "Category_Id"); + CreateIndex("dbo.Evaluations", "Student_Id"); + } + + /// + /// Operations to be performed during the downgrade process. + /// + public override void Down() + { + DropIndex("dbo.Evaluations", new[] { "Student_Id" }); + DropIndex("dbo.Evaluations", new[] { "Category_Id" }); + DropForeignKey("dbo.Evaluations", "Student_Id", "dbo.Students"); + DropForeignKey("dbo.Evaluations", "Category_Id", "dbo.Categories"); + AlterColumn("dbo.Evaluations", "Student_Id", c => c.Int()); + AlterColumn("dbo.Evaluations", "Category_Id", c => c.Int()); + CreateIndex("dbo.Evaluations", "Student_Id"); + CreateIndex("dbo.Evaluations", "Category_Id"); + AddForeignKey("dbo.Evaluations", "Student_Id", "dbo.Students", "Id"); + AddForeignKey("dbo.Evaluations", "Category_Id", "dbo.Categories", "Id"); + } + } +} diff --git a/StudentEvaluatorCore/Migrations/201310070731399_EvaluationInitialConstraints.resx b/StudentEvaluatorCore/Migrations/201310070731399_EvaluationInitialConstraints.resx new file mode 100644 index 0000000..7f54937 --- /dev/null +++ b/StudentEvaluatorCore/Migrations/201310070731399_EvaluationInitialConstraints.resx @@ -0,0 +1,123 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + text/microsoft-resx + + + 2.0 + + + System.Resources.ResXResourceReader, System.Windows.Forms, Version=4.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089 + + + System.Resources.ResXResourceWriter, System.Windows.Forms, Version=4.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089 + + + H4sIAAAAAAAEAN1azW7cNhC+F+g7CDq1BbKynQRIjXUCd9cugsQ/sJwcejFoibsmSpGqSBm7z9ZDH6mv0JFWoihS0kr7Vyc3LUV+HM58M5rh7L9//zP+sIio84wTQTg7c49HR66DWcBDwuZnbipnr965H97/+MP4IowWztdy3utsHqxk4sx9kjI+9TwRPOEIiVFEgoQLPpOjgEceCrl3cnT0q3d87GGAcAHLccZ3KZMkwvkP+DnhLMCxTBG94iGmohiHN36O6lyjCIsYBfjM/SNIR75MQ8zkxTOiKZI8GU3PP7vOOSUI5PExnblO/Ob0i8C+TDib+zGSBNH7ZYzh/QxRgQvpT+M3fQ9wdJIdwEOMcQlwnG2kAFcdDQ53AUqQy0ys/IAg+upc+iSY9gkvawMwdJvwGCdyeYdnxdKPoet49XWeuVAt09Zku8MTk69PXOc6pRQ9UqyUBFr0Qb/4d8xwgiQOb5GUOGHZWpxLb+1q7HELlOEM0es0esRJuR/YBQjmOpdkgcPPmM3lk9rzCi3KkWNg2RdGgI6wRiYpbhCxe/tLkgiZPQ7c+eTttjv7acIOsu81eibznJGGBIV7wAvhOneYrh6fSLzykVH1/qEknnOZ8OiO09rq8u3DPUrmGCbd89YpPk+TwJBx7FVE76R/hfddeQAnTIpynykOSISo69wm8FRE3Xeu4wco27Umwsr+3eh3GIksFg1iGTx20Kxx21aWTUApc54s11GsmtfIsfK1YlADydSckoh9ZVT03oUXdAhoOspGXqAU9R35wAYR+O3Wsf+KsP36Hki74w12Ecv7OlpHNLecsQ+Rz4XgAckBLKEfWkh9wUKnRyhY8aZ+TiBLSiWJKQlAmDP3F0uZ3eDq+BV4pbk69LFret4Nm2KKJXbOA5lbeYJEgELboKCrsKY7TUu9ldecFjYezwpSO1SdlQZU2CpyHlxxKy5CASERgehUKG/6WC8RYFE2BS9sJWbLfSzrXwpwsork5hktXdUhaq5qoehWWANU0JHgJpyKqwaKpqcmmbTwoM3siCKmBfu5rDqLoQ9vGFxJNQ1O14rJmfrZh+lFmbdTLY2u2NcZt1OK6X4aWsXa3hopo7jynKrm9lZFd1mcey3V+fgKxTF8ybVqvRhx/FWpPnnlDy+SoxWGF4iGWllJq3aCvATNsfEWtgZJ89pviiR6RFmyMAkja9rwOFFubIQL24Klv5YLsufVorYbjFELuSrFXsJZI5iRHxvbBLBX5lcoiKKkIembcJpGrC1x7FptFvU6kvmuP6pWq+uA2nB/LFV960hq0MYZe4aKTYN6lkWt71WdIb34o4eBnVCorYTuxaKuxXsiUpE+1whUjPVHKWtgHaUcezGm1r5bO7F02we6h53bl+7HyrZPD3VnrZTTYbThAVhV1VbDqoYPz5n6Z7otRmi526BQoK3r7/JZEtJahFoZmq2xXjxSME2EymuAEmAz6YrMa0PphgllJlvbGFolo8PsrJbtysxGzrmhHkuU3RrZyK5fiI2tvNqcokKJyq+NPHpc5LTrW2FWkruakl0/8WcSZgmuvxQSR6Nswsj/i04oyflRTrhCjMywkPf8TwzVDuTg74w+2gY9Lk+IkL7QRhdR3tF1mTnworG5x8WeURI8ocTqaG3bwmoEzhpH23WoNoP9Jpo7+7F67eo33Gtfp7TNTxFa/DwUqvaR1RQylC5aFO+L8g00PfbCjbXu+nZ4HLDaGbumnNXO2G6D/fQJ6inwwS7uWy7lNr5gB/biJCMXopApCJlArmAVFbcJYQGJETXPbuc6fbwiO5WCNN9McYxZRnf7jH12W5PIK3RDyevUsJ9+SS27PlQH40UwqP2G8f8mUHeNsHP+NLeN7FvjXt2irmbRKiWHWPrIwdSrWNl6Xd/eSlrTSWrapaPF0NZqWnY2mpo26ejGHK4RZUpfv+Tq115p6c68zB6TwbtaC+BQxx3QQLILWvBc7R+gEDsEmVcQ2f9BGQ5qPqvmfGQzXoYPQ6JyipnXYIlCcOjzRJIZCiS8DrAQ+Z9QvsLJM8VA4RZ+ZDepjFMJR8bRI60xJwtBXfvnXbK6zOObOHe+XRwBxCRwBHzDfksJDZXclw0pdwtEFtuKDDezpcwy3flSIV1z1hOoUJ8Kyfc4iimAiRvmo2fcLtt6HdY1Np4SNE9QJAqMaj38BPqF0eL9f7TedLLDLAAA + + \ No newline at end of file diff --git a/StudentEvaluatorCore/Migrations/Configuration.cs b/StudentEvaluatorCore/Migrations/Configuration.cs new file mode 100644 index 0000000..4d19df0 --- /dev/null +++ b/StudentEvaluatorCore/Migrations/Configuration.cs @@ -0,0 +1,40 @@ +using System.Data.Entity.Migrations; +using System.Runtime.CompilerServices; +using Zcu.StudentEvaluator.DAL; + +namespace Zcu.StudentEvaluator.Migrations +{ + /// + /// This namespace contains classes for upgrading/downgrading Entity Framework database. + /// + [CompilerGenerated] + internal class NamespaceDoc + { + //Trick to document a namespace + } + + internal sealed class Configuration : DbMigrationsConfiguration + { + public Configuration() + { + AutomaticMigrationsEnabled = true; //changed to automatically update database to the latest version - see http://msdn.microsoft.com/en-us/data/jj591621 + //AutomaticMigrationDataLossAllowed = true; + } + + protected override void Seed(DbStudentEvaluationContext context) + { + // This method will be called after migrating to the latest version. + + // You can use the DbSet.AddOrUpdate() helper extension method + // to avoid creating duplicate seed data. E.g. + // + // context.People.AddOrUpdate( + // p => p.FullName, + // new Person { FullName = "Andrew Peters" }, + // new Person { FullName = "Brice Lambson" }, + // new Person { FullName = "Rowan Miller" } + // ); + // + } + } +} diff --git a/StudentEvaluatorCore/Model/Category.cs b/StudentEvaluatorCore/Model/Category.cs new file mode 100644 index 0000000..93fd6f1 --- /dev/null +++ b/StudentEvaluatorCore/Model/Category.cs @@ -0,0 +1,60 @@ +using System.Collections.Generic; +using System.ComponentModel.DataAnnotations; + +namespace Zcu.StudentEvaluator.Model +{ + /// + /// This class contains settings for an evaluation + /// + public class Category : IEntity + { + /// + /// Gets or sets the unique identifier. + /// + /// + /// The unique identifier. + /// + [Key] + public int Id { get; set; } + + /// + /// Gets or sets the name of the evaluation. + /// + /// + /// The name of the evaluation, e.g. "Comments in code". + /// + [Required] + [MaxLength(50)] + public string Name { get; set; } + + /// + /// Gets or sets the minimal number of points required to pass. + /// + /// + /// The number of points to pass. + /// + public decimal? MinPoints { get; set; } + + /// + /// Gets or sets the maximal number of points that will count. + /// + /// + /// The maximal number of points that counts + /// + [CustomValidation(typeof(CustomValidator), "ValidateCategoryMaxPoints")] + public decimal? MaxPoints { get; set; } + + /// + /// Gets or sets the collection of evaluations. + /// + public virtual ICollection Evaluations { get; set; } + + /// + /// Initializes a new instance of the class. + /// + public Category() + { + this.Evaluations = new List(); + } + } +} diff --git a/StudentEvaluatorCore/Model/ClassDiagram.cd b/StudentEvaluatorCore/Model/ClassDiagram.cd new file mode 100644 index 0000000..6843a61 --- /dev/null +++ b/StudentEvaluatorCore/Model/ClassDiagram.cd @@ -0,0 +1,52 @@ + + + + + + AAACAAAAAAAAAAAAAAAAAASAAAAAAIAABAAAAAAAAAA= + Model\Category.cs + + + + + + + + + + AAACAAAAQAAAgAIAAAAAAAAAAAAAAAAAAAAAAAAACAA= + Model\Evaluation.cs + + + + + + + + + + + AAACAEAAAAAAAIAAAAAAAAAABAAAAAAABAAAAAAAACA= + Model\Student.cs + + + + + + + + + + AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAABAAAAAA= + Program.cs + + + + + + AAACAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA= + Model\IEntity.cs + + + + \ No newline at end of file diff --git a/StudentEvaluatorCore/Model/CustomValidator.cs b/StudentEvaluatorCore/Model/CustomValidator.cs new file mode 100644 index 0000000..cb83798 --- /dev/null +++ b/StudentEvaluatorCore/Model/CustomValidator.cs @@ -0,0 +1,68 @@ +using System.ComponentModel.DataAnnotations; +using System.Diagnostics.Contracts; +using System.Linq; + +namespace Zcu.StudentEvaluator.Model +{ + /// + /// Implements custom validations for Model. + /// + public class CustomValidator + { + /// + /// Validates the evaluation points. + /// + /// The points. + /// The validation context. + /// + /// true, if the value of "Points" of the give evaluation is valid; otherwise false and error message is returned in validationResult + /// + public static ValidationResult ValidateEvaluationPoints(decimal? points, ValidationContext validationContext) + { + Contract.Requires(validationContext != null); + + Evaluation evaluation = validationContext.ObjectInstance as Evaluation; + + if (evaluation.Category.MaxPoints != null && points != null && + points > evaluation.Category.MaxPoints) + { + return new ValidationResult("The number of points may not exceed the maximum (" + + evaluation.Category.MaxPoints + ") specified for category '" + + evaluation.Category.Name + "'."); + } + + return ValidationResult.Success; + } + + /// + /// Validates the maximal number of points. + /// + /// The maximum points. + /// The validation context. + /// + /// true, if the value of "MaxPoints" of the given category is valid; otherwise false and error message is returned in validationResult + /// + public static ValidationResult ValidateCategoryMaxPoints(decimal? maxPoints, ValidationContext validationContext) + { + Contract.Requires(validationContext != null); + + if (maxPoints != null) + { + Category category = validationContext.ObjectInstance as Category; + if (category.Evaluations.Count != 0) + { + var evaluation = (category.Evaluations.Where(x => x.Points != null && x.Points > category.MaxPoints) + .OrderByDescending(x => x.Points)).FirstOrDefault(); + if (evaluation != null) + { + return new ValidationResult("The maximal number of points cannot be set to " + + category.MaxPoints + " because at least one evaluation specifies the number of points that exceed this value." + + "The minimal allowed value is " + evaluation.Points + "."); + } + } + } + + return ValidationResult.Success; + } + } +} diff --git a/StudentEvaluatorCore/Model/Evaluation.cs b/StudentEvaluatorCore/Model/Evaluation.cs new file mode 100644 index 0000000..b0048d2 --- /dev/null +++ b/StudentEvaluatorCore/Model/Evaluation.cs @@ -0,0 +1,54 @@ +using System.ComponentModel.DataAnnotations; + +namespace Zcu.StudentEvaluator.Model +{ + /// + /// Evaluation object describes one particular evaluation in one evaluation parent (Category) for one student (Student) + /// + public class Evaluation : IEntity + { + /// + /// Gets or sets the unique identifier. + /// + /// + /// The unique identifier. + /// + [Key] + public int Id { get; set; } + + /// + /// Gets the number of points. + /// + /// + /// The number of points. + /// + [CustomValidation(typeof(CustomValidator), "ValidateEvaluationPoints")] + public decimal? Points { get; set; } + + /// + /// Gets the reason for the points given. + /// + /// + /// The reason for the points give, e.g. "the solution lacks OO design". + /// + public string Reason { get; set; } + + /// + /// Gets or sets the evaluation parent. + /// + /// + /// The definition. + /// + [Required] + public Category Category {get; set; } + + /// + /// Gets or sets the student to whom this evaluation belongs. + /// + /// + /// The student. + /// + [Required] + public Student Student {get; set; } + } +} \ No newline at end of file diff --git a/StudentEvaluatorCore/Model/IEntity.cs b/StudentEvaluatorCore/Model/IEntity.cs new file mode 100644 index 0000000..793f8da --- /dev/null +++ b/StudentEvaluatorCore/Model/IEntity.cs @@ -0,0 +1,28 @@ + +using System.Runtime.CompilerServices; +namespace Zcu.StudentEvaluator.Model +{ + /// + /// This namespace contains classes and interfaces for defining the data model. + /// + /// Model defines in-memory stored data entities that are used to exchange data to/from repositories. + [CompilerGenerated] + internal class NamespaceDoc + { + //Trick to document a namespace + } + + /// + /// Represents the entity (of the model) + /// + public interface IEntity + { + /// + /// Gets or sets the unique identifier. + /// + /// + /// The unique identifier. + /// + int Id { get; set; } + } +} diff --git a/StudentEvaluatorCore/Model/Student.cs b/StudentEvaluatorCore/Model/Student.cs new file mode 100644 index 0000000..adba118 --- /dev/null +++ b/StudentEvaluatorCore/Model/Student.cs @@ -0,0 +1,64 @@ +using System.Collections.Generic; +using System.ComponentModel.DataAnnotations; + +namespace Zcu.StudentEvaluator.Model +{ + /// + /// This structure keeps the number of points given and the reason for it. + /// + public class Student : IEntity + { + /// + /// Gets or sets the unique identifier. + /// + /// + /// The unique identifier. + /// + [Key] + public int Id { get; set; } + + /// + /// Gets or sets the personal number of the student. + /// + /// + /// The personal number, e.g. A12B0012P. + /// + [Required] + [MaxLength(10)] + [RegularExpression(@"[A-Z]\d{2}[BN]\d+[PK]")] + public string PersonalNumber { get; set; } //NOTE: Entity Framework does not support [Unique] attribute => this must be done in migrations + + /// + /// Gets or sets the first name. + /// + /// + /// The first name, e.g., "Josef". + /// + [Required] + [MaxLength(25)] + public string FirstName { get; set; } + + /// + /// Gets or sets the surname. + /// + /// + /// The surname, e.g., "Kohout". + /// + [Required] + [MaxLength(25)] + public string Surname { get; set; } + + /// + /// Gets or sets the individual student evaluation. + /// + public virtual ICollection Evaluations { get; set; } + + /// + /// Initializes a new instance of the class. + /// + public Student() + { + this.Evaluations = new List(); + } + } +} diff --git a/StudentEvaluatorCore/Properties/AssemblyInfo.cs b/StudentEvaluatorCore/Properties/AssemblyInfo.cs new file mode 100644 index 0000000..cc57099 --- /dev/null +++ b/StudentEvaluatorCore/Properties/AssemblyInfo.cs @@ -0,0 +1,35 @@ +using System.Reflection; +using System.Runtime.InteropServices; + +// General Information about an assembly is controlled through the following +// set of attributes. Change these attribute values to modify the information +// associated with an assembly. +[assembly: AssemblyTitle("StudentEvaluatorConsoleApp")] +[assembly: AssemblyDescription("")] +[assembly: AssemblyConfiguration("")] +[assembly: AssemblyCompany("")] +[assembly: AssemblyProduct("StudentEvaluatorConsoleApp")] +[assembly: AssemblyCopyright("Copyright © 2013")] +[assembly: AssemblyTrademark("")] +[assembly: AssemblyCulture("")] + +// Setting ComVisible to false makes the types in this assembly not visible +// to COM components. If you need to access a type in this assembly from +// COM, set the ComVisible attribute to true on that type. +[assembly: ComVisible(false)] + +// The following GUID is for the ID of the typelib if this project is exposed to COM +[assembly: Guid("86fe8914-8a59-4cff-8705-1b910356f887")] + +// Version information for an assembly consists of the following four values: +// +// Major Version +// Minor Version +// Build Number +// Revision +// +// You can specify all the values or you can default the Build and Revision Numbers +// by using the '*' as shown below: +// [assembly: AssemblyVersion("1.0.*")] +[assembly: AssemblyVersion("1.0.0.0")] +[assembly: AssemblyFileVersion("1.0.0.0")] diff --git a/StudentEvaluatorCore/Resources/Strings.Designer.cs b/StudentEvaluatorCore/Resources/Strings.Designer.cs new file mode 100644 index 0000000..e4ad321 --- /dev/null +++ b/StudentEvaluatorCore/Resources/Strings.Designer.cs @@ -0,0 +1,72 @@ +//------------------------------------------------------------------------------ +// +// This code was generated by a tool. +// Runtime Version:4.0.30319.34003 +// +// Changes to this file may cause incorrect behavior and will be lost if +// the code is regenerated. +// +//------------------------------------------------------------------------------ + +namespace Zcu.StudentEvaluator.Resources { + using System; + + + /// + /// A strongly-typed resource class, for looking up localized strings, etc. + /// + // This class was auto-generated by the StronglyTypedResourceBuilder + // class via a tool like ResGen or Visual Studio. + // To add or remove a member, edit your .ResX file then rerun ResGen + // with the /str option, or rebuild your VS project. + [global::System.CodeDom.Compiler.GeneratedCodeAttribute("System.Resources.Tools.StronglyTypedResourceBuilder", "4.0.0.0")] + [global::System.Diagnostics.DebuggerNonUserCodeAttribute()] + [global::System.Runtime.CompilerServices.CompilerGeneratedAttribute()] + internal class Strings { + + private static global::System.Resources.ResourceManager resourceMan; + + private static global::System.Globalization.CultureInfo resourceCulture; + + [global::System.Diagnostics.CodeAnalysis.SuppressMessageAttribute("Microsoft.Performance", "CA1811:AvoidUncalledPrivateCode")] + internal Strings() { + } + + /// + /// Returns the cached ResourceManager instance used by this class. + /// + [global::System.ComponentModel.EditorBrowsableAttribute(global::System.ComponentModel.EditorBrowsableState.Advanced)] + internal static global::System.Resources.ResourceManager ResourceManager { + get { + if (object.ReferenceEquals(resourceMan, null)) { + global::System.Resources.ResourceManager temp = new global::System.Resources.ResourceManager("Zcu.StudentEvaluator.Resources.Strings", typeof(Strings).Assembly); + resourceMan = temp; + } + return resourceMan; + } + } + + /// + /// Overrides the current thread's CurrentUICulture property for all + /// resource lookups using this strongly typed resource class. + /// + [global::System.ComponentModel.EditorBrowsableAttribute(global::System.ComponentModel.EditorBrowsableState.Advanced)] + internal static global::System.Globalization.CultureInfo Culture { + get { + return resourceCulture; + } + set { + resourceCulture = value; + } + } + + /// + /// Looks up a localized string similar to Do you want to permanently delete '{0}'?. + /// + internal static string DeleteSelectedStudents_Confirmation { + get { + return ResourceManager.GetString("DeleteSelectedStudents_Confirmation", resourceCulture); + } + } + } +} diff --git a/StudentEvaluatorCore/Resources/Strings.cs-CZ.Designer.cs b/StudentEvaluatorCore/Resources/Strings.cs-CZ.Designer.cs new file mode 100644 index 0000000..e69de29 diff --git a/StudentEvaluatorCore/Resources/Strings.cs-CZ.resx b/StudentEvaluatorCore/Resources/Strings.cs-CZ.resx new file mode 100644 index 0000000..a91d770 --- /dev/null +++ b/StudentEvaluatorCore/Resources/Strings.cs-CZ.resx @@ -0,0 +1,123 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + text/microsoft-resx + + + 2.0 + + + System.Resources.ResXResourceReader, System.Windows.Forms, Version=4.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089 + + + System.Resources.ResXResourceWriter, System.Windows.Forms, Version=4.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089 + + + Skutecne si prejete odstranit trvale '{0}'? + + \ No newline at end of file diff --git a/StudentEvaluatorCore/Resources/Strings.resx b/StudentEvaluatorCore/Resources/Strings.resx new file mode 100644 index 0000000..90337a8 --- /dev/null +++ b/StudentEvaluatorCore/Resources/Strings.resx @@ -0,0 +1,123 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + text/microsoft-resx + + + 2.0 + + + System.Resources.ResXResourceReader, System.Windows.Forms, Version=4.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089 + + + System.Resources.ResXResourceWriter, System.Windows.Forms, Version=4.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089 + + + Do you want to permanently delete '{0}'? + + \ No newline at end of file diff --git a/StudentEvaluatorCore/StudentEvaluatorCore.csproj b/StudentEvaluatorCore/StudentEvaluatorCore.csproj new file mode 100644 index 0000000..5bb3845 --- /dev/null +++ b/StudentEvaluatorCore/StudentEvaluatorCore.csproj @@ -0,0 +1,190 @@ + + + + + Debug + AnyCPU + {5F535E59-C1A7-4766-86F1-BFBF71CB1B37} + Library + Properties + Zcu.StudentEvaluator + StudentEvaluatorCore + v4.5 + 512 + 0 + + + AnyCPU + true + full + false + bin\Debug\ + DEBUG;TRACE + prompt + 4 + False + False + True + False + False + True + True + True + True + False + False + True + True + False + False + False + True + False + True + True + False + True + + + + + + + + True + True + Full + Build + 0 + bin\Debug\StudentEvaluatorCore.XML + + + AnyCPU + pdbonly + true + bin\Release\ + TRACE + prompt + 4 + bin\Release\StudentEvaluatorCore.XML + + + + + + + False + ..\packages\EntityFramework.6.0.1\lib\net45\EntityFramework.dll + + + ..\packages\EntityFramework.6.0.1\lib\net45\EntityFramework.SqlServer.dll + + + + + + + + + + + + + + + Strings.cs-CZ.resx + True + True + + + + + + + + + + + + + + 201310070645097_InitialCreate.cs + + + + 201310070652287_CategoryNameIsUnique.cs + + + + 201310070731399_EvaluationInitialConstraints.cs + + + + + + + + + + True + True + Strings.resx + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + ResXFileCodeGenerator + Strings.cs-CZ.Designer.cs + + + 201310070645097_InitialCreate.cs + + + 201310070652287_CategoryNameIsUnique.cs + + + 201310070731399_EvaluationInitialConstraints.cs + + + ResXFileCodeGenerator + Strings.Designer.cs + + + + + + + + \ No newline at end of file diff --git a/StudentEvaluatorCore/View/IConfirmationView.cs b/StudentEvaluatorCore/View/IConfirmationView.cs new file mode 100644 index 0000000..1e6389e --- /dev/null +++ b/StudentEvaluatorCore/View/IConfirmationView.cs @@ -0,0 +1,138 @@ +using System.Diagnostics.Contracts; +namespace Zcu.StudentEvaluator.View +{ + /// + /// Contains values that identify the way in which the confirmation dialog box was closed. + /// + [System.Flags] + public enum ConfirmationResult + { + /// + /// Unknown - must ask user differently (error) + /// + Ask = 0, + + /// + /// The dialog box return value is OK (usually sent from a button labeled OK). + /// + OK = 1, + + /// + /// The dialog box return value is Cancel (usually sent from a button labeled Cancel). + /// + Cancel = 2, + + /// + /// The dialog box return value is Abort (usually sent from a button labeled Abort). + /// + Abort = 4, + + /// + /// The dialog box return value is Retry (usually sent from a button labeled Retry). + /// + Retry = 8, + + /// + /// The dialog box return value is Ignore (usually sent from a button labeled Ignore). + /// + Ignore = 16, + + /// + /// The dialog box return value is Yes (usually sent from a button labeled Yes). + /// + Yes = 32, + + /// + /// The dialog box return value is No (usually sent from a button labeled No). + /// + No = 64, + + /// + /// The dialog box return value is Yes (usually sent from a button labeled Yes To All). + /// "Yes" should be returned automatically to all other successive confirmation requests of the same kind. + /// + YesToAll = 128, + + /// + /// The dialog box return value is No (usually sent from a button labeled No To All). + /// "No" should be returned automatically to all other successive confirmation requests of the same kind. + /// + NoToAll = 256, + } + + /// + /// Contains options that may be used in confirmation requests + /// + public enum ConfirmationOptions + { + + /// + /// The message box contains an OK button. + /// + OK = ConfirmationResult.OK, + + /// + /// The ok cancel + /// + OKCancel = ConfirmationResult.OK | ConfirmationResult.Cancel, //The message box contains OK and Cancel buttons. + + /// + /// The message box contains Abort, Retry, and Ignore buttons. + /// + AbortRetryIgnore = ConfirmationResult.Abort | ConfirmationResult.Retry | ConfirmationResult.Ignore, + + /// + /// The message box contains Yes, No, and Cancel buttons. + /// + YesNoCancel = ConfirmationResult.Yes | ConfirmationResult.No | ConfirmationResult.Cancel, + + /// + /// The message box contains Yes and No buttons. + /// + YesNo = ConfirmationResult.Yes | ConfirmationResult.No, + + /// + /// The message box contains Retry and Cancel buttons. + /// + RetryCancel = ConfirmationResult.Retry | ConfirmationResult.Cancel, + + /// + /// The message box contains Yes, No, Yes To All and No To All buttons. + /// + YesYesoAllNoTNoToAll = YesNo | ConfirmationResult.YesToAll | ConfirmationResult.NoToAll, + + /// + /// The message box contains Yes, No, Yes To All, No To All, and Cancel buttons. + /// + YesYesoAllNoTNoToAllCancel = YesNoCancel | ConfirmationResult.YesToAll | ConfirmationResult.NoToAll, + } + + /// + /// This represents dialog with the user to confirm some action, e.g., closing the document without saving. + /// + [ContractClass(typeof(ContractClassForIConfirmationView))] + public interface IConfirmationView + { + /// + /// Confirms the action to be done. + /// + /// Options available during the confirmation. + /// The caption, i.e., a short summary of what is needed to be confirmed. + /// The detailed explanation of what is to be confirmed. + /// User decision. + ConfirmationResult ConfirmAction(ConfirmationOptions options, string caption, string message); + } + + [ContractClassFor(typeof(IConfirmationView))] + abstract class ContractClassForIConfirmationView : IConfirmationView + { + public ConfirmationResult ConfirmAction(ConfirmationOptions options, string caption, string message) + { + Contract.Requires(caption != null); + Contract.Requires(message != null); + + throw new System.NotImplementedException(); + } + } + +} diff --git a/StudentEvaluatorCore/View/INotificationView.cs b/StudentEvaluatorCore/View/INotificationView.cs new file mode 100644 index 0000000..01fda13 --- /dev/null +++ b/StudentEvaluatorCore/View/INotificationView.cs @@ -0,0 +1,57 @@ +using System; +using System.Runtime.CompilerServices; +namespace Zcu.StudentEvaluator.View +{ + /// + /// This namespace contains classes/interfaces implementing Views. + /// + /// A View represents the current state of the ViewModel associated with this View + /// using various visual controls, i.e., it actually represents GUI, e.g., a dialog or form. + /// + [CompilerGenerated] + internal class NamespaceDoc + { + //Trick to document a namespace + } + + /// + /// Enumeration of possible notifications. + /// + public enum NotificationType + { + /// + /// The notification has only informational character (message). + /// + Message, + + /// + /// The notification is a warning. + /// + Warning, + + /// + /// The notification is an error. Something has gone wrong. + /// + Error, + + /// + /// The notification is a severe error that might lead to application crash. + /// + FatalError, + } + + /// + /// This represents a notification system for notifying the user of any change in the application, e.g., "The requested item does not exist". + /// + public interface INotificationView + { + /// + /// Displays the notification message to the user. + /// + /// The type of the notification. + /// The caption of the message, i.e., this is a short summary of what has happened. + /// The message to be displayed containing the detailed explanation of what has happened. + /// The exception containing all the details (may be null). + void DisplayNotification(NotificationType type, string caption, string message, Exception exc = null); + } +} diff --git a/StudentEvaluatorCore/View/IWindowView.cs b/StudentEvaluatorCore/View/IWindowView.cs new file mode 100644 index 0000000..0b3d651 --- /dev/null +++ b/StudentEvaluatorCore/View/IWindowView.cs @@ -0,0 +1,47 @@ +using System; +using System.Collections.Generic; +using System.Linq; +using System.Text; +using System.Threading.Tasks; + +namespace Zcu.StudentEvaluator.View +{ + /// + /// Represents a generic View + /// + public interface IWindowView + { + /// + /// Gets or sets the data context. + /// + /// + /// The data context implementing IViewModel and other interfaces. + /// + object DataContext {get; set; } + + /// + /// Opens a window and returns without waiting for the newly opened window to close. + /// + void Show(); + + /// + /// Manually closes a Window. + /// + void Close(); + + /// + /// Gets or sets the dialog result value, which is the value that is returned from the ShowDialog method. + /// + /// + /// A value of type . The default is false. + /// + bool? DialogResult { get; set; } + + /// + /// Opens a window and returns only when the newly opened window is closed. + /// + /// A value of type that specifies whether the activity was accepted (true) or cancelled (false). + /// The return value is the value of the DialogResult property before a window closes. + bool? ShowDialog(); + } +} diff --git a/StudentEvaluatorCore/ViewModel/ClassDiagram_Classes.cd b/StudentEvaluatorCore/ViewModel/ClassDiagram_Classes.cd new file mode 100644 index 0000000..ffddc18 --- /dev/null +++ b/StudentEvaluatorCore/ViewModel/ClassDiagram_Classes.cd @@ -0,0 +1,96 @@ + + + + + + AAAAgAAAAAAAAAAAAAAAAAAAAAAAgAAAAAAEAAAQACA= + ViewModel\StudentsListWorkspace\EvaluationViewModel.cs + + + + + + AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA= + ViewModel\RelayCommand.cs + + + + + + + + + AAAAAAAAQEACAAQAAECAAAAAAAAAAAAAAAAAAAAAAAA= + ViewModel\PartialClasses\RelayCommandInternal.cs + + + + + + + AAAAAAACAAAAAAAAAAAAgAAAAAAAgAAABQAEIAAAAAA= + ViewModel\StudentsListWorkspace\StudentListItemViewModel.cs + + + + + + + + + + + + + + + + + + RAQAAAAAAAgASQEAAASEAEAgAjAAgAUAQAAEAhAEgAA= + ViewModel\StudentsListWorkspace\StudentListViewModel.cs + + + + + + + + + + wAQAEEIAAAABUIEEAAAEAAiABAGgAAEoAUAAAAAEACA= + ViewModel\StudentDetailWorkspace\StudentViewModel.cs + + + + + + + + + + + + ViewModel\ViewModel.cs + + + + + AAACgRAgAAAgAAACAEEAABMAAICgAGAKAAgAAIAABAA= + ViewModel\ViewModel.cs + + + + + + + + + + AAEAEAAAACAAAoQQACAAAQAQgABAAAAAAAABAAIAAAA= + ViewModel\ViewModelBase.cs + + + + + \ No newline at end of file diff --git a/StudentEvaluatorCore/ViewModel/ClassDiagram_Interfaces.cd b/StudentEvaluatorCore/ViewModel/ClassDiagram_Interfaces.cd new file mode 100644 index 0000000..62d905c --- /dev/null +++ b/StudentEvaluatorCore/ViewModel/ClassDiagram_Interfaces.cd @@ -0,0 +1,73 @@ + + + + + + AAAAAEAAAAAAAIAAAAAAAAAABAAAAAAAAAAAAAAAACA= + ViewModel\StudentDetailWorkspace\IStudentViewModel.cs + + + + + + AAAAAAACAAAAAAAAAAAAAAAAAAAAAAAABQAEAAAAAAA= + ViewModel\StudentsListWorkspace\IStudentListItemViewModel.cs + + + + + + + + + + + + AAAAAAAAAAAAAAAAAACAAEAAAAAAAAAAAAAEAAAAAAA= + ViewModel\StudentsListWorkspace\IStudentListViewModel.cs + + + + + + QAAAAAAAAAAAQAEAAAAEAAAAAAAAAAAAAAAAAAAEAAA= + ViewModel\IEditableViewModel.cs + + + + + + AAAAAAAAAAAACAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA= + ViewModel\IListViewModel.cs + + + + + + AAAAAAAAAAgAAQAAAAAAAAAAAAAAAAAAAAAAAAAAAAA= + ViewModel\ISelectableListViewModel.cs + + + + + + AAAAAAAAAAABEAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA= + ViewModel\ISelectableViewModel.cs + + + + + + AAACABAAAAAAAAACAAAAABAAAACAAAAAAAAAAAAAAAA= + ViewModel\IViewModel.cs + + + + + + AAAAEAAAAAAAAAAQAAAAAAAAAAAAAAAAAAAAAAAAAAA= + ViewModel\IViewModelBase.cs + + + + \ No newline at end of file diff --git a/StudentEvaluatorCore/ViewModel/IEditableViewModel.cs b/StudentEvaluatorCore/ViewModel/IEditableViewModel.cs new file mode 100644 index 0000000..998746f --- /dev/null +++ b/StudentEvaluatorCore/ViewModel/IEditableViewModel.cs @@ -0,0 +1,41 @@ +using System; +using System.Windows.Input; + +namespace Zcu.StudentEvaluator.ViewModel +{ + /// + /// Represents ViewModel that supports CRUD commands. + /// + public interface IEditableViewModel : IViewModelBase + { + /// + /// Gets the Create command. + /// + /// Supports creating a new model. The model is set to be the current one and marked as being edited. + ICommand CreateCommand { get; } + + /// + /// Gets the edit command. + /// + /// Supports editing the current model. The changes are typically not stored until SaveCommand is executed. + ICommand EditCommand { get; } + + /// + /// Gets the save command. + /// + /// Supports saving the changes done to the current model into the repository. + ICommand SaveCommand { get; } + + /// + /// Gets the cancel command. + /// + /// Supports cancelling the changes done to the current model. + ICommand CancelCommand { get; } + + /// + /// Gets the delete command. + /// + /// Supports deleting the current model from the repository. + ICommand DeleteCommand { get; } + } +} diff --git a/StudentEvaluatorCore/ViewModel/IListViewModel.cs b/StudentEvaluatorCore/ViewModel/IListViewModel.cs new file mode 100644 index 0000000..e1e2cf1 --- /dev/null +++ b/StudentEvaluatorCore/ViewModel/IListViewModel.cs @@ -0,0 +1,16 @@ +using System; +using System.Collections.ObjectModel; + +namespace Zcu.StudentEvaluator.ViewModel +{ + /// + /// Represents a ViewModel that is a list (collection) of ViewModel items. + /// + public interface IListViewModel : IViewModelBase + { + /// + /// Gets the items in the collection (list). + /// + ObservableCollection Items { get; } + } +} diff --git a/StudentEvaluatorCore/ViewModel/ISelectableListViewModel.cs b/StudentEvaluatorCore/ViewModel/ISelectableListViewModel.cs new file mode 100644 index 0000000..75f0826 --- /dev/null +++ b/StudentEvaluatorCore/ViewModel/ISelectableListViewModel.cs @@ -0,0 +1,21 @@ +using System; +using System.Collections.ObjectModel; + +namespace Zcu.StudentEvaluator.ViewModel +{ + /// + /// Represents a ViewModel that can be selected and/or focused in Views + /// + public interface ISelectableListViewModel : IListViewModel + { + /// + /// Gets the selected items in the collection (list). + /// + ObservableCollection SelectedItems { get; } + + /// + /// Gets the currently focused item in the collection (list). + /// + T FocusedItem { get; } + } +} diff --git a/StudentEvaluatorCore/ViewModel/ISelectableViewModel.cs b/StudentEvaluatorCore/ViewModel/ISelectableViewModel.cs new file mode 100644 index 0000000..392df9c --- /dev/null +++ b/StudentEvaluatorCore/ViewModel/ISelectableViewModel.cs @@ -0,0 +1,20 @@ +using System; + +namespace Zcu.StudentEvaluator.ViewModel +{ + /// + /// Represents a ViewModel that can be selected and/or focused in Views + /// + public interface ISelectableViewModel : IViewModelBase + { + /// + /// Gets/sets whether this item is focused in the UI. + /// + bool IsFocused { get; set; } + + /// + /// Gets/sets whether this item is selected in the UI. + /// + bool IsSelected { get; set; } + } +} diff --git a/StudentEvaluatorCore/ViewModel/IViewModel.cs b/StudentEvaluatorCore/ViewModel/IViewModel.cs new file mode 100644 index 0000000..95c3e9f --- /dev/null +++ b/StudentEvaluatorCore/ViewModel/IViewModel.cs @@ -0,0 +1,50 @@ +using System; +using System.ComponentModel; + +namespace Zcu.StudentEvaluator.ViewModel +{ + /// + /// Represents a generic ViewModel + /// + public interface IViewModel : IViewModelBase + { + /// + /// Gets the model unique identifier. + /// + int Id { get; } + + /// + /// Gets a value indicating whether this object is marked for deletion. + /// + /// Any operation with the ViewModel in this state is ignored. + /// + /// true if the object is marked to be deleted; otherwise, false. + /// + bool IsModelDeleted { get; } + + /// + /// Gets a value indicating whether any property of the underlying model has changed since the last save operation. + /// + /// + /// true if any model property has changed; otherwise, false. + /// + bool IsModelDirty { get; } + + /// + /// Gets a value indicating whether the underlying model is new. + /// + /// A new model is such an object that has never been stored into the repository. + /// + /// true if the underlying model is new; otherwise, false. + /// + bool IsModelNew { get; } + + /// + /// Gets a value indicating whether the model is valid. + /// + /// + /// true if the model is valid; otherwise, false. + /// + bool IsModelValid { get; } + } +} diff --git a/StudentEvaluatorCore/ViewModel/IViewModelBase.cs b/StudentEvaluatorCore/ViewModel/IViewModelBase.cs new file mode 100644 index 0000000..4f0bee4 --- /dev/null +++ b/StudentEvaluatorCore/ViewModel/IViewModelBase.cs @@ -0,0 +1,39 @@ +using System; +using System.ComponentModel; +using System.Runtime.CompilerServices; + +namespace Zcu.StudentEvaluator.ViewModel +{ + /// + /// This namespace contains classes/interfaces implementing ViewModels. + /// + /// A ViewModel represents the logic related to its underlying Model (data) + /// as well as the logic related to the visual presentation of the data to the user (via Views). + /// + [CompilerGenerated] + internal class NamespaceDoc + { + //Trick to document a namespace + } + + /// + /// Represents a very simple generic ViewModel + /// + public interface IViewModelBase + { + /// + /// Returns the user-friendly name of this object. + /// Child classes can set this property to a new value, + /// or override it to determine the value on-demand. + /// + string DisplayName { get; } + + /// + /// Gets a value indicating whether the ViewModel is read only. + /// + /// + /// true if the ViewModel is read only; otherwise, false. + /// + bool IsReadOnly { get; } + } +} diff --git a/StudentEvaluatorCore/ViewModel/RelayCommand.cs b/StudentEvaluatorCore/ViewModel/RelayCommand.cs new file mode 100644 index 0000000..5901d6d --- /dev/null +++ b/StudentEvaluatorCore/ViewModel/RelayCommand.cs @@ -0,0 +1,169 @@ +using System; +using System.Diagnostics; +using System.Windows.Input; + +namespace Zcu.StudentEvaluator.ViewModel +{ + /// + /// Represents a simple tool to override the body of RelayCommand.CanExecuteChanged event + /// and related RaiseCanExecuteChanged method from applications + /// + public static class RelayCommandInjector + { + /// + /// Gets or sets the delegate to an action to be executed from CanExecuteChanged.Add routine. + /// + /// + /// The action that is to be called have one parameter which is the delegate passed in: CanExecuteChanged += delegate. + /// + public static Action CanExecuteChangedAddAction { get; set; } + + /// + /// Gets or sets the delegate to an action to be executed from CanExecuteChanged.Remove routine. + /// + /// + /// The action that is to be called have one parameter which is the delegate passed in: CanExecuteChanged -= delegate. + /// + public static Action CanExecuteChangedRemoveAction { get; set; } + + /// + /// Gets or sets the action to be called from RaiseCanExecuteChangedAction. + /// + /// + /// The action has one parameter which will be the reference to the caller. + /// + public static Action RaiseCanExecuteChangedAction { get; set; } + } + + internal class RelayCommand : RelayCommand + { + /// + /// Initializes a new instance of the class. + /// + /// The execution logic. + public RelayCommand(Action execute) : base(execute) + { + + } + + /// + /// Initializes a new instance of the class. + /// + /// The execution logic. + /// The execution status logic. + public RelayCommand(Action execute, Predicate canExecute) + : base(execute, canExecute) + { + + } + } + + internal class RelayCommand : ICommand + { + #region Fields + readonly Action _execute; + readonly Predicate _canExecute; + #endregion // Fields + + /// + /// Creates a new command that can always execute. + /// + /// The execution logic. + public RelayCommand(Action execute) + : this(execute, null) + { + } + + /// + /// Creates a new command. + /// + /// The execution logic. + /// The execution status logic. + public RelayCommand(Action execute, Predicate canExecute) + { + if (execute == null) + throw new ArgumentNullException("execute"); + + _execute = execute; + _canExecute = canExecute; + } + + /// + /// Defines the method that determines whether the command can execute in its current state. + /// + /// Data used by the command. If the command does not require data to be passed, this object can be set to null. + /// + /// true if this command can be executed; otherwise, false. + /// + [DebuggerStepThrough] + public bool CanExecute(object parameter) + { + return _canExecute == null ? true : _canExecute(parameter != null ? (T)parameter : default(T)); + } + + /// + /// Defines the method to be called when the command is invoked. + /// + /// Data used by the command. If the command does not require data to be passed, this object can be set to null. + public void Execute(object parameter) + { + if (CanExecute(parameter)) + _execute(parameter != null ? (T)parameter : default(T)); + } + + + /// + /// Internal CanExecuteChanged back-end. + /// + /// + /// When an event has add and remove accessors specified, it cannot be raised directly (compiler error). + /// For the details, see http://csharpindepth.com/Articles/Chapter2/Events.aspx. + /// This internal event therefore is here to hack it. + /// + private event EventHandler _CanExecuteChanged; + + /// + /// Occurs when changes occur that affect whether the command should execute. + /// + public event EventHandler CanExecuteChanged + { + add + { + lock (this) + { + if (RelayCommandInjector.CanExecuteChangedAddAction != null) + RelayCommandInjector.CanExecuteChangedAddAction(value); + else + this._CanExecuteChanged += value; //this is the default implementation + } + } + remove + { + lock (this) + { + if (RelayCommandInjector.CanExecuteChangedRemoveAction != null) + RelayCommandInjector.CanExecuteChangedRemoveAction(value); + else + this._CanExecuteChanged -= value; //this is the default implementation + } + } + } + + /// + /// Raises the event. + /// + public void RaiseCanExecuteChanged() + { + if (RelayCommandInjector.RaiseCanExecuteChangedAction != null) + RelayCommandInjector.RaiseCanExecuteChangedAction(this); + else + { + //default implementation + if (this._CanExecuteChanged != null) + { + this._CanExecuteChanged(this, EventArgs.Empty); + } + } + } + } +} \ No newline at end of file diff --git a/StudentEvaluatorCore/ViewModel/StudentDetailWorkspace/IStudentViewModel.cs b/StudentEvaluatorCore/ViewModel/StudentDetailWorkspace/IStudentViewModel.cs new file mode 100644 index 0000000..2b7275d --- /dev/null +++ b/StudentEvaluatorCore/ViewModel/StudentDetailWorkspace/IStudentViewModel.cs @@ -0,0 +1,41 @@ +using System; +namespace Zcu.StudentEvaluator.ViewModel +{ + /// + /// This interface represent a student viewModel + /// + public interface IStudentViewModel : IViewModel + { + /// + /// Gets or sets the personal number of the student. + /// + /// + /// The personal number, e.g. A12B0012P. + /// + string PersonalNumber { get; set; } + + /// + /// Gets or sets the first name. + /// + /// + /// The first name, e.g., "Josef". + /// + string FirstName { get; set; } + + /// + /// Gets or sets the surname. + /// + /// + /// The surname, e.g., "Kohout". + /// + string Surname { get; set; } + + /// + /// Gets the full name of the student. + /// + /// + /// The full name. + /// + string FullName { get; } + } +} diff --git a/StudentEvaluatorCore/ViewModel/StudentDetailWorkspace/StudentViewModel.cs b/StudentEvaluatorCore/ViewModel/StudentDetailWorkspace/StudentViewModel.cs new file mode 100644 index 0000000..c374948 --- /dev/null +++ b/StudentEvaluatorCore/ViewModel/StudentDetailWorkspace/StudentViewModel.cs @@ -0,0 +1,385 @@ +using System; +using System.Collections.Generic; +using System.Windows.Input; +using Zcu.StudentEvaluator.DAL; +using Zcu.StudentEvaluator.Model; +using Zcu.StudentEvaluator.View; +using System.Linq; + +namespace Zcu.StudentEvaluator.ViewModel +{ + /// + /// This ViewModel represents data of one particular student. + /// + public class StudentViewModel : ViewModel, + IEditableViewModel, ISelectableViewModel, IStudentViewModel + { + #region Fields + private bool _isFocused = false; //currently not focused + private bool _isSelected = false; //currently not selected + + private ICommand _editCommand; //definition of the command to edit the student personal data + private ICommand _saveCommand; //definition of the command to accept changes + private ICommand _cancelCommand; //definition of the command to cancel changes + private ICommand _deleteCommand; //definition of the command to delete the student (from model) + #endregion + + #region Constructor + /// + /// Initializes a new instance of the class with a new model. + /// + /// The model repository. + public StudentViewModel(IRepository modelRepository = null) + : base(modelRepository) + { + + } + + /// + /// Initializes a new instance of the class. + /// + /// The model to be wrapped. + /// The model repository. + /// Model state is supposed to be Unchanged. + /// model cannot be null + public StudentViewModel(Student model, IRepository modelRepository = null) + : base(model, modelRepository) + { + + } + + /// + /// Initializes a new instance of the class. + /// + /// The model to be wrapped. + /// The model repository. + /// State of the model. + /// model cannot be null + protected StudentViewModel(Student model, ModelStates modelState, IRepository modelRepository = null) + : base(model, modelState, modelRepository) + { + + } + #endregion // Constructor + + #region Model Properties + /// + /// Gets or sets the personal number of the student. + /// + /// + /// The personal number, e.g. A12B0012P. + /// + public string PersonalNumber + { + get + { + return GetModelPropertyValue(); + } + + set + { + if (SetModelPropertyValue(value: value)) + NotifyPropertyChanged(() => this.DisplayName); + } + } + + /// + /// Gets or sets the first name. + /// + /// + /// The first name, e.g., "Josef". + /// + public string FirstName + { + get + { + return GetModelPropertyValue(); + } + set + { + if (SetModelPropertyValue(value: value)) + { + NotifyPropertyChanged(() => this.DisplayName); + NotifyPropertyChanged(() => this.FullName); + } + } + } + + /// + /// Gets or sets the surname. + /// + /// + /// The surname, e.g., "Kohout". + /// + public string Surname + { + get + { + return GetModelPropertyValue(); + } + set + { + if (SetModelPropertyValue(value: value)) + { + NotifyPropertyChanged(() => this.DisplayName); + NotifyPropertyChanged(() => this.FullName); + } + } + } + #endregion + + #region Derived Model Properties + /// + /// Gets the full name of the student. + /// + /// + /// The full name. + /// + public string FullName + { + get + { + if (this.Surname != null) + { + return (this.FirstName != null) ? Surname.ToUpper() + " " + FirstName : Surname.ToUpper(); + } + else if (this.FirstName != null) + { + return FirstName; + } + else + return null; + } + } + #endregion + + #region Presentation Properties + /// + /// Returns the user-friendly name of this object. + /// Child classes can set this property to a new value, + /// or override it to determine the value on-demand. + /// + public override string DisplayName + { + get + { + return (this.PersonalNumber == null) ? this.FullName : + this.FullName + " (" + this.PersonalNumber + ")"; + } + } + + /// + /// Gets/sets whether this student is selected in the UI. + /// + public bool IsSelected + { + get { return _isSelected; } + set + { + if (value == _isSelected) + return; + + _isSelected = value; + NotifyPropertyChanged(); + } + } + + /// + /// Gets/sets whether this student is focused in the UI. + /// + public bool IsFocused + { + get { return _isFocused; } + set + { + if (value == _isFocused) + return; + + _isFocused = value; + NotifyPropertyChanged(); + } + } + #endregion + + #region Commands + /// + /// Gets the Create command. + /// + /// Not implemented by this class. + /// + /// Supports creating a new model. The model is set to be the current one and marked as being edited. + /// + public ICommand CreateCommand + { + get { throw new System.NotImplementedException(); } + } + + /// + /// Gets the edit command. + /// + /// When executed, the ViewModel is set to EditMode, which allows editing properties of the model. + /// View is notified about this change through the change of IsReadOnly property. + public ICommand EditCommand + { + get + { + if (_editCommand == null) + _editCommand = new RelayCommand( + execute: param => EditStudent(), + canExecute: param => CanEditStudent() + ); + + return _editCommand; + } + } + + /// + /// Gets the save command. + /// + public ICommand SaveCommand + { + get + { + if (_saveCommand == null) + _saveCommand = new RelayCommand( + execute: param => SaveChanges(), + canExecute: param => CanSaveChanges() + ); + + return _saveCommand; + } + } + + /// + /// Gets the cancel command. + /// + public ICommand CancelCommand + { + get + { + if (_cancelCommand == null) + _cancelCommand = new RelayCommand( + execute: param => CancelChanges(), + canExecute: param => CanCancelChanges() + ); + + return _cancelCommand; + } + } + + /// + /// Gets the delete command. + /// + public ICommand DeleteCommand + { + get + { + if (_deleteCommand == null) + _deleteCommand = new RelayCommand( + execute: param => DeleteStudent(), + canExecute: param => CanDeleteStudent() + ); + + return _deleteCommand; + } + } + #endregion + + #region BusinessLogic + /// + /// Determines whether the student can be deleted in the current context. + /// + /// true, if the student can be delete, false otherwise + virtual protected bool CanEditStudent() + { + //Student can be deleted only if it is not currently being edited and has not been already deleted + return this.IsReadOnly && !this.IsModelDeleted; + } + + /// + /// Determines whether changes can be saved. + /// + /// true, if SaveChanges can be executed, false otherwise + virtual protected bool CanSaveChanges() + { + //Changes can be saved only if a) object is in edit mode, + //b) there are some changes and c) the object changes are valid + return !this.IsReadOnly && this.IsModelDirty && this.IsModelValid; + } + + /// + /// Determines whether changes can be cancelled in the current context. + /// + /// true, if CancelChanges can be executed, false otherwise + virtual protected bool CanCancelChanges() + { + return !this.IsReadOnly && this.IsModelDirty; + } + + /// + /// Determines whether the student can be deleted in the current context. + /// + /// true, if the student can be delete, false otherwise + virtual protected bool CanDeleteStudent() + { + //Student can be deleted only if it is not currently being edited, i.e., if the student is valid and exists in the repository + return this.IsReadOnly && !this.IsModelDeleted; + } + + /// + /// Switch to edit mode + /// + virtual protected void EditStudent() + { + this.IsReadOnly = false; + } + + /// + /// Deletes the student. + /// + virtual protected void DeleteStudent() + { + var personalNumber = this.PersonalNumber ?? ""; + + if (DeleteModel(confQuestion: + "Do you really want to remove the student with personal number '" + + personalNumber + "' from the repository?") + ) + { + DisplayNotification(NotificationType.Message, "Student deleted", + "Student with personal number '" + personalNumber + "' has been removed from the repository."); + } + } + + /// + /// Saves the changes. + /// + virtual protected void SaveChanges() + { + bool isNew = this.IsModelNew; + if (SaveModelChanges()) + { + if (isNew) + DisplayNotification(NotificationType.Message, + "Student created", "A new student with personal number '" + this.PersonalNumber + "' has been added into the repository."); + else + DisplayNotification(NotificationType.Message, + "Student updated", "Student with personal number '" + this.PersonalNumber + "' has been updated."); + + this.IsReadOnly = true; + } + } + + /// + /// Cancel the changes done to the currently edited student (must be called after EditStudent). + /// + virtual protected void CancelChanges() + { + if (CancelModelChanges()) + this.IsReadOnly = true; + } + + + #endregion + } +} diff --git a/StudentEvaluatorCore/ViewModel/StudentsListWorkspace/EvaluationViewModel.cs b/StudentEvaluatorCore/ViewModel/StudentsListWorkspace/EvaluationViewModel.cs new file mode 100644 index 0000000..8d07dbd --- /dev/null +++ b/StudentEvaluatorCore/ViewModel/StudentsListWorkspace/EvaluationViewModel.cs @@ -0,0 +1,80 @@ +using System; +using Zcu.StudentEvaluator.DAL; +using Zcu.StudentEvaluator.Model; + +namespace Zcu.StudentEvaluator.ViewModel +{ + /// + /// TODO: create THIS + /// + public class EvaluationViewModel : ViewModel + { + /// + /// Gets the model (of evaluation) associated with this ViewModel. + /// + protected Evaluation Evaluation { get; private set; } + + /// + /// Gets the repository of the model (evaluation). + /// + protected IStudentEvaluationUnitOfWork UnitOfWork { get; private set; } + + + /// + /// Initializes a new instance of the class. + /// + /// The evaluation. + /// The unit of work. + /// + /// evaluation + /// or + /// unitOfWork + /// + public EvaluationViewModel(Evaluation evaluation, IStudentEvaluationUnitOfWork unitOfWork) + { + if (evaluation == null) + throw new ArgumentNullException("evaluation"); + + if (unitOfWork == null) + throw new ArgumentNullException("unitOfWork"); + + this.Evaluation = evaluation; + this.UnitOfWork = unitOfWork; + } + + /// + /// Initializes a new instance of the class. + /// + /// The model. + /// The repository. + public EvaluationViewModel(Evaluation model, IRepository repository) + : base(model, repository) + { + + } + + /// + /// Gets or sets the valid points. + /// + /// + /// The valid points. + /// + public decimal? ValidPoints { get; set; } + + /// + /// Gets or sets the valid points reason. + /// + /// + /// The valid points reason. + /// + public string ValidPointsReason { get; set; } + + /// + /// Gets or sets a value indicating whether [has passed]. + /// + /// + /// true if [has passed]; otherwise, false. + /// + public bool HasPassed { get; set; } + } +} diff --git a/StudentEvaluatorCore/ViewModel/StudentsListWorkspace/IStudentListItemViewModel.cs b/StudentEvaluatorCore/ViewModel/StudentsListWorkspace/IStudentListItemViewModel.cs new file mode 100644 index 0000000..6bc0205 --- /dev/null +++ b/StudentEvaluatorCore/ViewModel/StudentsListWorkspace/IStudentListItemViewModel.cs @@ -0,0 +1,30 @@ +using System; +using System.Collections.ObjectModel; +namespace Zcu.StudentEvaluator.ViewModel +{ + /// + /// This interface represents information of a student to be presented in lists of students. + /// + public interface IStudentListItemViewModel : IStudentViewModel, ISelectableViewModel + { + /// + /// Gets the evaluations (ViewModels) of this Student View Model. + /// + ObservableCollection Evaluations { get; } + + /// + /// Gets the total points the student obtained. + /// + decimal? TotalPoints { get; } + + /// + /// Gets the reason for the total points given + /// + string TotalPointsReason { get; } + + /// + /// Gets a value indicating whether the student has passed + /// + bool HasPassed { get; } + } +} diff --git a/StudentEvaluatorCore/ViewModel/StudentsListWorkspace/IStudentListViewModel.cs b/StudentEvaluatorCore/ViewModel/StudentsListWorkspace/IStudentListViewModel.cs new file mode 100644 index 0000000..7ab624c --- /dev/null +++ b/StudentEvaluatorCore/ViewModel/StudentsListWorkspace/IStudentListViewModel.cs @@ -0,0 +1,25 @@ + +using System.Windows.Input; +namespace Zcu.StudentEvaluator.ViewModel +{ + /// + /// ViewModel / Controller for manipulation with students. + /// + public interface IStudentListViewModel : ISelectableListViewModel, IEditableViewModel + { + /// + /// Gets the refresh list command that can be used to refresh the content of the list. + /// + ICommand RefreshListCommand { get; } + + /// + /// Gets the number of students in the list. + /// + int AllStudentsCount { get; } + + /// + /// Gets the number of students in the list with HasPassed. + /// + int HasPassedStudentsCount { get; } + } +} diff --git a/StudentEvaluatorCore/ViewModel/StudentsListWorkspace/StudentListItemViewModel.cs b/StudentEvaluatorCore/ViewModel/StudentsListWorkspace/StudentListItemViewModel.cs new file mode 100644 index 0000000..3dd7d17 --- /dev/null +++ b/StudentEvaluatorCore/ViewModel/StudentsListWorkspace/StudentListItemViewModel.cs @@ -0,0 +1,206 @@ +using System; +using System.Collections.Generic; +using System.Collections.ObjectModel; +using System.ComponentModel; +using System.Diagnostics; +using System.Linq; +using System.Text; +using System.Threading.Tasks; +using System.Windows.Input; +using Zcu.StudentEvaluator.DAL; +using Zcu.StudentEvaluator.Model; + +namespace Zcu.StudentEvaluator.ViewModel +{ + /// + /// This ViewModel represents data of one particular student. + /// + public class StudentListItemViewModel : StudentViewModel, IStudentListItemViewModel + { + /// + /// Gets the repository of the model (student). + /// + protected IStudentEvaluationUnitOfWork UnitOfWork { get; private set; } + + /// + /// Initializes a new instance of the class with a new model. + /// + /// The unit of work. + public StudentListItemViewModel(IStudentEvaluationUnitOfWork unitOfWork = null) : base() + { + this.UnitOfWork = unitOfWork; + if (this.UnitOfWork != null) + this.ModelRepository = this.UnitOfWork.Students; + } + + /// + /// Initializes a new instance of the class. + /// + /// The model to be wrapped. + /// The unit of work. + /// student cannot be null + public StudentListItemViewModel(Student model, IStudentEvaluationUnitOfWork unitOfWork = null) + : base(model) + { + this.UnitOfWork = unitOfWork; + if (this.UnitOfWork != null) + this.ModelRepository = this.UnitOfWork.Students; + } + + /// + /// Initializes a new instance of the class. + /// + /// The model to be wrapped. + /// State of the model. + /// The unit of work. + /// model cannot be null + protected StudentListItemViewModel(Student model, ModelStates modelState, IStudentEvaluationUnitOfWork unitOfWork = null) + : base(model, modelState) + { + this.UnitOfWork = unitOfWork; + if (this.UnitOfWork != null) + this.ModelRepository = this.UnitOfWork.Students; + } + + #region Derived Model Properties + + /// + /// Gets the evaluations (ViewModels) of this Student View Model. + /// + public ObservableCollection Evaluations + { + get + { + return GetModelDerivedPropertyValue>( + defaultSelector: () => ConstructEvaluationCollection()); + } + } + + /// + /// Gets the total points the student obtained. + /// + public decimal? TotalPoints + { + get + { + //return the cached value whenever possible + return GetModelDerivedPropertyValue( + defaultSelector: () => this.Evaluations.Sum(x => x.ValidPoints)); + } + } + + /// + /// Gets the reason for the total points given + /// + public string TotalPointsReason + { + get + { + return GetModelDerivedPropertyValue( + defaultSelector: () => + { + var sb = new StringBuilder(); + foreach (var item in this.Evaluations) + { + //TODO: zakomentovat tento test a ukazat moznosti Diggeru + if (item == null) + continue; + + sb.AppendFormat("{0}\n", item.ValidPointsReason); + } + + return sb.ToString(); + }); + } + } + + /// + /// Gets a value indicating whether the student has passed + /// + public bool HasPassed + { + get + { + return GetModelDerivedPropertyValue( + defaultSelector: () => this.Evaluations.All(x => x.HasPassed)); + } + } + + #region Evaluation Collections Changes + /// + /// Constructs the ViewModel evaluation collection for Model evaluation collection. + /// + /// Upon construction, the object registers itself to listen notify changes of evaluations + /// The constructed collection + protected virtual ObservableCollection ConstructEvaluationCollection() + { + var evaluations = new ObservableCollection(); + + foreach (var item in this.Model.Evaluations) //lazy loading + { + //create a wrapper + var evaluation = new EvaluationViewModel(item, this.UnitOfWork != null ? this.UnitOfWork.Evaluations : null); + evaluation.PropertyChanged += OnEvaluationPropertyChanged; //register us to Notify + evaluations.Add(evaluation); + } + + evaluations.CollectionChanged += OnEvaluationCollectionChange; + return evaluations; + } + + /// + /// Called when the evaluation collection change. + /// + /// The sender. + /// The instance containing the event data. + /// + private void OnEvaluationCollectionChange(object sender, System.Collections.Specialized.NotifyCollectionChangedEventArgs e) + { + //reset aggregation values + this.RemoveModelDerivedPropertyCacheEntry(() => this.HasPassed); + this.RemoveModelDerivedPropertyCacheEntry(() => this.TotalPoints); + this.RemoveModelDerivedPropertyCacheEntry(() => this.TotalPointsReason); + + if (e.OldItems != null) + { + foreach (EvaluationViewModel item in e.OldItems) + { + item.PropertyChanged -= OnEvaluationPropertyChanged; + } + } + + if (e.NewItems != null) + { + foreach (EvaluationViewModel item in e.NewItems) + { + item.PropertyChanged += OnEvaluationPropertyChanged; + } + } + } + + /// + /// Called when some property of evaluation ViewModel changed. + /// + /// The sender (evaluation ViewModel). + /// The instance containing the event data. + private void OnEvaluationPropertyChanged(object sender, System.ComponentModel.PropertyChangedEventArgs e) + { + var ev = sender as EvaluationViewModel; + + // Make sure that the property name we're referencing is valid. + // This is a debugging technique, and does not execute in a Release build. + ev.VerifyPropertyName(e.PropertyName); + + // When ValidPoints has changed, we need to invalidate TotalPoints + // so that it will be queried again for a new value. + if (e.PropertyName == GetPropertyName(() => ev.ValidPoints)) + this.RemoveModelDerivedPropertyCacheEntry(() => this.TotalPoints); + else if (e.PropertyName == GetPropertyName(() => ev.ValidPointsReason)) + this.RemoveModelDerivedPropertyCacheEntry(() => this.TotalPointsReason); + else if (e.PropertyName == GetPropertyName(() => ev.HasPassed)) + this.RemoveModelDerivedPropertyCacheEntry(() => this.HasPassed); + } + #endregion + #endregion + } +} diff --git a/StudentEvaluatorCore/ViewModel/StudentsListWorkspace/StudentListViewModel.cs b/StudentEvaluatorCore/ViewModel/StudentsListWorkspace/StudentListViewModel.cs new file mode 100644 index 0000000..a9a9f4f --- /dev/null +++ b/StudentEvaluatorCore/ViewModel/StudentsListWorkspace/StudentListViewModel.cs @@ -0,0 +1,473 @@ +using System; +using System.Collections.ObjectModel; +using System.Data; +using System.Data.Entity.Validation; +using System.Linq; +using System.Text; +using Zcu.StudentEvaluator.DAL; +using Zcu.StudentEvaluator.Model; +using Zcu.StudentEvaluator.View; +using System.Windows.Input; +using System.Collections.Specialized; +using System.ComponentModel; +using System.Diagnostics; + +namespace Zcu.StudentEvaluator.ViewModel +{ + /// + /// Represents an observable collection of StudentListItemViewModels with associated commands + /// + public class StudentListViewModel : ViewModelBase, IStudentListViewModel + { + #region Fields + private ObservableCollection _items; //All students + + private ICommand _createCommand; //definition of the command to create new student + private ICommand _editCommand; //definition of the command to edit the currently focused student personal data + private ICommand _deleteCommand; //definition of the command to delete the currently selected students + private ICommand _refreshCommand; //definition of the command to refresh the list + #endregion + + #region IListViewModel + /// + /// Gets the items in the collection (list). + /// + public ObservableCollection Items + { + get + { + return _items; + } + protected set + { + if (_items == value) + return; //no change + + //unregister from the original list + if (_items != null) + { + foreach (var item in _items.ToList()) + { + item.PropertyChanged -= Items_PropertyChanged; + } + + _items.CollectionChanged -= Items_CollectionChanged; + } + + _items = value; + + //register to the new list + if (_items != null) + { + _items.CollectionChanged += Items_CollectionChanged; + + foreach (var item in _items.ToList()) + { + item.PropertyChanged += Items_PropertyChanged; + } + } + + NotifyPropertyChanged(); + + RemoveModelDerivedPropertyCacheEntry(() => FocusedItem); + RemoveModelDerivedPropertyCacheEntry(() => SelectedItems); + + RemoveModelDerivedPropertyCacheEntry(() => HasPassedStudentsCount); + + NotifyPropertyChanged(() => AllStudentsCount); + } + } + #endregion + + #region ISelectableListViewModel + /// + /// Gets the selected items in the collection (list). + /// + public ObservableCollection SelectedItems + { + get + { + return GetModelDerivedPropertyValue( + defaultSelector: + () => new ObservableCollection + (this.Items.Where(x => x.IsSelected)) + ); + } + } + + /// + /// Gets the currently focused item in the collection (list). + /// + public StudentListItemViewModel FocusedItem + { + get + { + return GetModelDerivedPropertyValue( + defaultSelector: + () => this.Items.FirstOrDefault(x => x.IsFocused) + ); + } + } + #endregion + + #region IStudentListViewModel + /// + /// Gets the number of students in the list. + /// + public int AllStudentsCount + { + get { return this.Items.Count; } + } + + /// + /// Gets the number of students in the list with HasPassed. + /// + public int HasPassedStudentsCount + { + get { + return GetModelDerivedPropertyValue( + defaultSelector: + () => this.Items.Count(x => x.HasPassed) + ); + } + } + #endregion + + /// + /// Gets the unit of work (with all repositories). + /// + protected IStudentEvaluationUnitOfWork UnitOfWork { get; private set; } + + /// + /// Initializes a new instance of the class. + /// + /// The unit of work. + public StudentListViewModel(IStudentEvaluationUnitOfWork unitOfWork = null) + { + this.UnitOfWork = unitOfWork; + base.DisplayName = "Students"; + + //populate the list of students + this.PopulateList(); + } + + #region Commands + /// + /// Gets the Create command. + /// + /// + /// + /// Supports creating a new model. The model is set to be the current one and marked as being edited. + /// + public ICommand CreateCommand + { + get + { + if (_createCommand == null) + _createCommand = new RelayCommand( + execute: param => CreateNewStudent(), + canExecute: param => CanCreateNewStudent() + ); + + return _createCommand; + } + + } + + /// + /// Gets the edit command. + /// + /// + /// Supports editing the current model. The changes are typically not stored until SaveCommand is executed. + /// + public ICommand EditCommand + { + get + { + if (_editCommand == null) + _editCommand = new RelayCommand( + execute: param => EditFocusedStudent(), + canExecute: param => CanEditFocusedStudent() + ); + + return _editCommand; + } + } + + /// + /// Gets the save command. + /// + /// Not available + /// + /// Supports saving the changes done to the current model into the repository. + /// + public ICommand SaveCommand + { + get { throw new NotImplementedException(); } + } + + /// + /// Gets the cancel command. + /// + /// Not available + /// + /// Supports cancelling the changes done to the current model. + /// + public ICommand CancelCommand + { + get { throw new NotImplementedException(); } + } + + /// + /// Gets the delete command. + /// + /// + /// Supports deleting the current model from the repository. + /// + public ICommand DeleteCommand + { + get + { + if (_deleteCommand == null) + _deleteCommand = new RelayCommand( + execute: param => DeleteSelectedStudents(), + canExecute: param => CanDeleteSelectedStudents() + ); + + return _deleteCommand; + } + } + + /// + /// Gets the refresh list command that can be used to refresh the content of the list. + /// + public ICommand RefreshListCommand + { + get + { + if (_refreshCommand == null) + _refreshCommand = new RelayCommand( + execute: param => RefreshList() + ); + + return _refreshCommand; + } + } + #endregion + + #region Commands Business Logic + /// + /// Determines whether a new student can be created in the current context. + /// + /// true, if a new student can be added, false otherwise + virtual protected bool CanCreateNewStudent() + { + //Students can be added only if the list is not ReadOnly + return !this.IsReadOnly; + } + + /// + /// Determines whether the student can be edited in the current context. + /// + /// true, if the currently focused student can be deleted, false otherwise + virtual protected bool CanEditFocusedStudent() + { + //Students can be edited only if the list is not ReadOnly and there is some Focused student + return !this.IsReadOnly && this.FocusedItem != null; + } + + /// + /// Determines whether the student can be deleted in the current context. + /// + /// true, if the selected students can be deleted, false otherwise + virtual protected bool CanDeleteSelectedStudents() + { + //Students can be deleted only if the list is not ReadOnly and there are some selected students + return !this.IsReadOnly && this.SelectedItems.Count != 0; + } + + /// + /// Create a new student + /// + virtual protected void CreateNewStudent() + { + var window = DialogService.DialogService.Default.Get(DialogService.DialogConstants.EditStudentView); + if (window == null) + { + Debug.Fail("Unable to retrieve EditStudentView"); + return; + } + + var viewModel = new StudentListItemViewModel(this.UnitOfWork); + window.DataContext = viewModel; + + if (window.ShowDialog() == true) + { + //the new item has been accepted, so it is time to add the new item into the list + if (!viewModel.IsModelDeleted) //unless it was deleted + { + this.Items.Add(viewModel); + + viewModel.IsFocused = true; + this.Items.ToList().ForEach(x => x.IsSelected = false); + + viewModel.IsSelected = true; + } + } + } + + /// + /// Edit the currently focused student + /// + virtual protected void EditFocusedStudent() + { + var window = DialogService.DialogService.Default.Get(DialogService.DialogConstants.EditStudentView); + if (window == null) + { + Debug.Fail("Unable to retrieve EditStudentView"); + return; + } + + var viewModel = this.FocusedItem; + window.DataContext = viewModel; + + if (window.ShowDialog() == true) + { + //if the item has been deleted, remove it from the list + if (viewModel.IsModelDeleted) + this.Items.Remove(viewModel); + } + } + + /// + ///Delete the currently selected students + /// + virtual protected void DeleteSelectedStudents() + { + ConfirmationResult confResult = ConfirmationResult.Ask; + + var listToDelete = this.SelectedItems.ToList(); + foreach (var item in listToDelete) + { + if (!item.DeleteModel(ConfirmationOptions.YesYesoAllNoTNoToAll, + String.Format(Resources.Strings.DeleteSelectedStudents_Confirmation, item.DisplayName), + ref confResult) || confResult == ConfirmationResult.NoToAll + ) + break; //fatal error or terminated + + if (item.IsModelDeleted) //if the item has been successfully deleted, remove it from the list + this.Items.Remove(item); + } + + //list is automatically refreshed + } + + #endregion + + /// + /// Populates the list with the new data. + /// + protected void PopulateList() + { + if (this.UnitOfWork != null) + { + this.Items = new ObservableCollection( + this.UnitOfWork.Students.Get(includeProperties: new string[] { "Evaluations" }) + .Select(x => new StudentListItemViewModel(x, this.UnitOfWork))); //wrap Student into the appropriate ViewModel + } + } + + /// + /// Populates the list with the new data. + /// + protected void RefreshList() + { + if (this.UnitOfWork != null) + { + this.Items = new ObservableCollection( + this.UnitOfWork.Students.Get(includeProperties: new string[] { "Evaluations" }).AsParallel() + .Select(x => + { + var existing = this.Items.SingleOrDefault(y => y.Id == x.Id); + return existing ?? new StudentListItemViewModel(x, this.UnitOfWork); + } + )); //wrap Student into the appropriate ViewModel + } + } + + #region On Items Collection Changed + /// + /// Handles the CollectionChanged event of the Items control. + /// + /// The source of the event. + /// The instance containing the event data. + private void Items_CollectionChanged(object sender, NotifyCollectionChangedEventArgs e) + { + if (e.NewItems != null) + { + //register ourself as observers of ViewModels of items + foreach (StudentListItemViewModel custVM in e.NewItems) + custVM.PropertyChanged += this.Items_PropertyChanged; + } + + if (e.OldItems != null) + { + foreach (StudentListItemViewModel custVM in e.OldItems) + custVM.PropertyChanged -= this.Items_PropertyChanged; + } + + NotifyPropertyChanged(() => AllStudentsCount); + + RemoveModelDerivedPropertyCacheEntry(() => FocusedItem); + RemoveModelDerivedPropertyCacheEntry(() => SelectedItems); + + RemoveModelDerivedPropertyCacheEntry(() => HasPassedStudentsCount); + + } + + /// + /// Handles the PropertyChanged event of the Items control. + /// + /// The source of the event. + /// The instance containing the event data. + private void Items_PropertyChanged(object sender, PropertyChangedEventArgs e) + { + var it = sender as StudentListItemViewModel; + + // Make sure that the property name we're referencing is valid. + // This is a debugging technique, and does not execute in a Release build. + it.VerifyPropertyName(e.PropertyName); + + // When ValidPoints has changed, we need to invalidate TotalPoints + // so that it will be queried again for a new value. + if (e.PropertyName == GetPropertyName(() => it.IsSelected)) + { + this.RemoveModelDerivedPropertyCacheEntry(() => this.SelectedItems); + + if (it.IsSelected && !it.IsFocused) + { + //the item is newly selected but it has not been focused (yet) + if (this.FocusedItem != null) + this.FocusedItem.IsFocused = false; //only one item can have focus + + it.IsFocused = true; //set the focus onto the item + } + } + else if (e.PropertyName == GetPropertyName(() => it.IsFocused)) + { + if (it.IsFocused && it != this.FocusedItem && this.FocusedItem != null) + this.FocusedItem.IsFocused = false; //only one item can have focus + + this.RemoveModelDerivedPropertyCacheEntry(() => this.FocusedItem); + } + else if (e.PropertyName == GetPropertyName(() => it.HasPassed)) + this.RemoveModelDerivedPropertyCacheEntry(() => this.HasPassedStudentsCount); + else if (e.PropertyName == GetPropertyName(() => it.IsModelDeleted)) + { + if (it.IsModelDeleted) + this.Items.Remove(it); //remove the item from the list + } + } + #endregion + } +} diff --git a/StudentEvaluatorCore/ViewModel/ViewModel.cs b/StudentEvaluatorCore/ViewModel/ViewModel.cs new file mode 100644 index 0000000..41606ac --- /dev/null +++ b/StudentEvaluatorCore/ViewModel/ViewModel.cs @@ -0,0 +1,607 @@ +using System; +using System.Collections.Generic; +using System.ComponentModel; +using System.ComponentModel.DataAnnotations; +using System.Data.Entity.Validation; +using System.Diagnostics; +using System.Linq; +using System.Linq.Expressions; +using System.Reflection; +using System.Runtime.CompilerServices; +using System.Text; +using Zcu.StudentEvaluator.DAL; +using Zcu.StudentEvaluator.Model; +using Zcu.StudentEvaluator.View; + +namespace Zcu.StudentEvaluator.ViewModel +{ + /// + /// Base class for all ViewModel classes in the application. + /// It provides support for property change notifications and has a DisplayName property. This class is abstract. + /// + /// + /// ViewModel is a wrapper of Model and provides four kinds of properties: + /// a) Model Property = R/W property that provides direct R/W access to the same named property of the underlying model (e.g., FirstName) + /// b) Model Derived Property = R property that provides some product of values in model properties, e.g., FullName, DisplayName + /// c) Presentation Property = R/W property that stores some hints about how the ViewModel should be visualised, e.g., IsSelected + /// d) Another Property = special property, typically, private or protected + /// + /// All properties in a), b), c) groups notifies the listeners about changes done (via INotifyPropertyChanged). Properties in b) group + /// are being cached so that their product calculation is not necessary to repeat all the time. The cached value is released when + /// a change of such a property is notified (typically from some property in group a)). The cached is also released when + /// ResetModelCache is called (causes notification of changes, so that listeners may retrieve new values). Properties in a) group + /// are "cached" upon its first modification (as originalValues). When CancelModelChanges is called originalValues are copied to properties + /// (causes notification of changes). When SaveModelChanges is called, this "cache" is released, i.e., originalValues are released. + /// + ///INotifyPropertyChanged = standard interface for notifying listener that some property has changed. When WPF binding is being + ///established, the WPF core register itself as a listener of the source (an object implementing INotifyPropertyChanged) so as + ///whenever the source property changes, the underlying code passes the new value to the target property (using appropriate convertor) + ///IDataErrorInfo = standard interface for notifying caller about validation errors. When WPF binding is established + ///(with ValidatesOnDataErrors=True), after the value is passed from target property to the source property, + ///source object's IDataErrorInfo is used to get validation error. + /// + public abstract class ViewModel : ViewModelBase, IViewModel, IDataErrorInfo + where M : class, IEntity, new() + { + #region Constructor + /// + /// Initializes a new instance of the class with a new model. + /// + /// The model repository. + public ViewModel(IRepository modelRepository = null) + { + this.Model = new M(); + this.ModelState = this._originalModelState = ModelStates.Added; + this.ModelRepository = modelRepository; + + this.IsReadOnly = false; //default is View Only + } + + /// + /// Initializes a new instance of the class. + /// + /// The model to be wrapped. + /// The model repository. + /// Model state is unchanged. + /// model cannot be null + public ViewModel(M model, IRepository modelRepository = null) + { + if (model == null) + throw new ArgumentNullException("model"); + + this.Model = model; + this.ModelState = this._originalModelState = ModelStates.Unchanged; + this.ModelRepository = modelRepository; + + this.IsReadOnly = true; //default is View Only + } + + /// + /// Initializes a new instance of the class. + /// + /// The model to be wrapped. + /// The model repository. + /// State of the model. + /// model cannot be null + protected ViewModel(M model, ModelStates modelState, IRepository modelRepository = null) + { + if (model == null) + throw new ArgumentNullException("model"); + + this.Model = model; + this.ModelState = this._originalModelState = modelState; + this.ModelRepository = modelRepository; + + this.IsReadOnly = modelState.HasFlag(ModelStates.Deleted) || modelState == ModelStates.Unchanged; //default is View Only + } + #endregion // Constructor + + #region Model Base + /// + /// This flags define states in which the underlying model might reside. + /// + [System.Flags] + public enum ModelStates + { + /// + /// There has been no change in the model, so far, i.e., buttons such as "Save" may be disabled + /// + Unchanged = 0, + + /// + /// The model has been added and, therefore, it must be either saved or discarded => "Save" button should be enabled + /// + Added = 1, + + /// + /// Some property of the model has been changed => "Save" button should be enabled + /// + Updated = 2, + + /// + /// The model object has been deleted and, therefore, any other operation with this object is ignored + /// + Deleted = 4, + } + + private ModelStates _originalModelState; //original model state + private ModelStates _modelState; //current model state + + /// + /// Gets the underlying model. + /// + protected M Model { get; set; } + + /// + /// Gets the repository of the model. + /// + protected IRepository ModelRepository { get; set; } + + /// + /// Gets or sets the state of the model. + /// + /// + /// The new state of the model. + /// + protected ModelStates ModelState + { + get { return _modelState; } + set + { + if (_modelState == value) + return; + + _modelState = value; + NotifyPropertyChanged(); + NotifyPropertyChanged(() => this.IsModelNew); + NotifyPropertyChanged(() => this.IsModelDirty); + NotifyPropertyChanged(() => this.IsModelDeleted); + } + } + + /// + /// Gets a value indicating whether any property of the underlying model has changed since the last save operation. + /// + /// + /// true if any model property has changed; otherwise, false. + /// + public bool IsModelDirty + { + get + { + return !_modelState.HasFlag(ModelStates.Deleted) && + (_modelState.HasFlag(ModelStates.Added) || _modelState.HasFlag(ModelStates.Updated)); + } + } + + /// + /// Gets a value indicating whether the underlying model is new. + /// + /// A new model is such an object that has never been stored into the repository. + /// + /// true if the underlying model is new; otherwise, false. + /// + public bool IsModelNew + { + get + { + return !_modelState.HasFlag(ModelStates.Deleted) && + _modelState.HasFlag(ModelStates.Added); + } + } + + /// + /// Gets a value indicating whether this object is marked for deletion. + /// + /// Any operation with the ViewModel in this state is ignored. + /// + /// true if the object is marked to be deleted; otherwise, false. + /// + public bool IsModelDeleted + { + get + { + return _modelState.HasFlag(ModelStates.Deleted); + } + } + #endregion + + #region Model Properties + /// + /// Gets the unique identifier. + /// + public int Id + { + //It is read only attribute + get { return this.Model.Id; } + } + #endregion + + #region Model Properties Caching + private Dictionary _modelPropertiesCache = new Dictionary(); //cache for Model Properties + private bool _modelPropertiesCacheEnabled = true; //when set to false, _modelPropertiesCache is not used + + /// + /// Gets the model property value. + /// + /// Name of the property (in Model). + /// The value + protected T GetModelPropertyValue([CallerMemberName] string propertyName = null) + { + //use reflection to get the property value from the underlying model + return (T)this.Model.GetType().InvokeMember(propertyName, BindingFlags.GetProperty, null, this.Model, null); + //return (T)TypeDescriptor.GetProperties(this.Model)[propertyName].GetValue(this.Model); --- looks nicer but this is at least 4x slower + } + + /// + /// Sets the model property value. + /// + /// type of the value + /// Name of the property. + /// The new value. + /// The method is supposed to be called from setters of the Model Properties of ViewModel. + /// The call is successful only if the new value is not equal the current value. Upon the first successful call, + /// the current value is stored in order to support undo (unless _modelPropertiesCacheEnabled is false). Whenever the value is changed, + /// automatic notification is raised. Caller property is supposed to raise then notification of a change of any derived value. + /// true, if value was successfully set; false otherwise (e.g. the new value is the same as the old one) + protected bool SetModelPropertyValue([CallerMemberName] string propertyName = null, T value = default(T)) + { + if (this.IsReadOnly) + throw new InvalidOperationException("object is read-only"); + + if (ModelState.HasFlag(ModelStates.Deleted)) + return false; //operation is ignored + + T originalValue = GetModelPropertyValue(propertyName); //get the original value + if (EqualityComparer.Default.Equals(originalValue, value)) //check if the value is new + return false; //no change + + if (_modelPropertiesCacheEnabled) + { + if (_modelPropertiesCache.Count == 0) + _originalModelState = this.ModelState; + + //store the original value into the cache + if (!_modelPropertiesCache.ContainsKey(propertyName)) + _modelPropertiesCache.Add(propertyName, originalValue); + } + + this.ModelState |= ModelStates.Updated; + + //store the value into underlying Model using reflection + this.Model.GetType().InvokeMember(propertyName, BindingFlags.SetProperty, + null, this.Model, new object[] {value}); + + NotifyPropertyChanged(propertyName); + return true; + } + + /// + /// Cancels the changes done to the model property selected by the given selector. + /// + /// Name of the property. + protected void UndoModelChanges(string propertyName) + { + object value; + if (_modelPropertiesCache.TryGetValue(propertyName, out value)) + { + bool oldState = _modelPropertiesCacheEnabled; + _modelPropertiesCacheEnabled = false; //prevent adding the original value into the cache + + //invoke calling Setter of this object + this.GetType().InvokeMember(propertyName, BindingFlags.SetProperty, + null, this, new object[] { value }); + + _modelPropertiesCache.Remove(propertyName); //removes the entry + _modelPropertiesCacheEnabled = oldState; //restore the original caching policy + } + } + + /// + /// Cancels the changes done to the model property selected by the given selector. + /// + /// The selector of the property in the form of lambda expression: ()=>PropertyName. + protected void UndoModelChanges(Expression> propertyNameSelector) + { + UndoModelChanges(GetPropertyName(propertyNameSelector)); + } + + /// + /// Cancels all the changes done. + /// + /// true, if the changes have been successfully reversed, false, otherwise + virtual internal bool CancelModelChanges() + { + ConfirmationResult result = ConfirmationResult.Yes; + return CancelModelChanges(ConfirmationOptions.YesNoCancel, null, ref result); + } + + /// + /// Cancels all the changes done. + /// + /// The options for confirmation dialog. Yes, No, YesNoCancel, ... + /// The confirmation question to be displayed to the user. + /// The input/output result from confirmation. + /// true, if the model changes has been successfully cancelled, false otherwise + /// Confirmation dialog is initiated only if confResult [in] is set to Ask, otherwise the result passed in confResult + /// is used as confirmation result. This method allows non-silent undoing of model changes. + /// true, if the changes have been successfully reversed, false, otherwise + virtual internal bool CancelModelChanges(ConfirmationOptions confOptions, string confQuestion, ref ConfirmationResult confResult) + { + if (_modelPropertiesCache.Count == 0) + return true; //nothing to cancel + + if (confResult == ConfirmationResult.Ask) + { + if (!ConfirmAction(confOptions, "Cancel changes", + confQuestion ?? "Do you really want to continue without saving changes?", ref confResult) + ) + { + confResult = ConfirmationResult.NoToAll; + return false; + } + } + + if (confResult != ConfirmationResult.Yes && confResult != ConfirmationResult.YesToAll) + return false; //item is not to be deleted + + foreach (var item in _modelPropertiesCache.Keys.ToList()) //we need .ToList because we are going to modify the collection + { + UndoModelChanges(item); + } + + this.ModelState = _originalModelState; //return to the previous state + return true; + } + + /// + /// Saves all the changes done into the repository. + /// + /// true, if the changes have been successfully reversed, false, otherwise + virtual internal bool SaveModelChanges() + { + ConfirmationResult result = ConfirmationResult.Yes; + return SaveModelChanges(ConfirmationOptions.YesNoCancel, null, ref result); + } + + /// + /// Saves all the changes done into the repository. + /// + /// The options for confirmation dialog. Yes, No, YesNoCancel, ... + /// The confirmation question to be displayed to the user. + /// The input/output result from confirmation. + /// true, if the model has been saved successfully, false otherwise + /// Confirmation dialog is initiated only if confResult [in] is set to Ask, otherwise the result passed in confResult + /// is used as confirmation result. This method allows non-silent saving of model changes. + /// true, if the changes have been successfully reversed, false, otherwise + virtual internal bool SaveModelChanges(ConfirmationOptions confOptions, string confQuestion, ref ConfirmationResult confResult) + { + //if the model has not been marked as deleted + if (this.IsModelDeleted) + return true; //this is ignored + + if (confResult == ConfirmationResult.Ask) + { + if (!ConfirmAction(confOptions, "Save changes", + confQuestion ?? "Do you really want to save all changes?", ref confResult) + ) + { + confResult = ConfirmationResult.NoToAll; + return false; + } + } + + if (confResult != ConfirmationResult.Yes && confResult != ConfirmationResult.YesToAll) + return false; //item is not to be saved + + if (this.ModelRepository != null) + { + //TODO: handle concurrency problems + try + { + if (this.IsModelNew) + this.ModelRepository.Insert(this.Model); + else + this.ModelRepository.Update(this.Model); + + this.ModelRepository.Save(); //save all data + } + catch (Exception e) + { + var sb = new StringBuilder("Saving the changes has failed because of the following reason(s):"); + sb.AppendLine(); + + //validation error + DbEntityValidationException ev = e as DbEntityValidationException; + if (ev != null) + { + foreach (var entry in ev.EntityValidationErrors) + { + foreach (var it in entry.ValidationErrors) + { + sb.AppendFormat("{0}.{1} = {2}: {3}\n", + entry.Entry.Entity.GetType().Name, + it.PropertyName, + entry.Entry.CurrentValues[it.PropertyName], + it.ErrorMessage + ); + } + } + } + + //notify the user about the error + DisplayNotification(NotificationType.Error, "Saving failed", sb.ToString(), e); + return false; + } + } + + //remove every entry from the _modelPropertiesCache + _modelPropertiesCache.Clear(); + + //and set the model state to Unchanged + this.ModelState = ModelStates.Unchanged; + return true; + } + + /// + /// Delete the model object from the repository + /// + /// The options for confirmation dialog. + /// The confirmation question to be displayed to the user. + /// true, if the model has been deleted successfully, false otherwise + virtual internal bool DeleteModel(ConfirmationOptions confOptions = ConfirmationOptions.YesNo, string confQuestion = null) + { + ConfirmationResult result = ConfirmationResult.Ask; + return DeleteModel(confOptions, confQuestion, ref result); + } + + /// + /// Delete the model object from the repository + /// + /// The options for confirmation dialog. Yes, No, YesNoCancel, ... + /// The confirmation question to be displayed to the user. + /// The input/output result from confirmation. + /// true, if the model has been deleted successfully, false otherwise + /// Confirmation dialog is initiated only if confResult [in] is set to Ask, otherwise the result passed in confResult + /// is used as confirmation result. This allows silent deletion, or YesToAll deletion for collections of ViewModels. + virtual internal bool DeleteModel(ConfirmationOptions confOptions, string confQuestion, ref ConfirmationResult confResult) + { + //if the model is already deleted + if (this.IsModelDeleted) + return true; //ignore this request + + if (confResult == ConfirmationResult.Ask) + { + if (!ConfirmAction(confOptions, "Delete item", + confQuestion ?? "Do you really want to delete (permanently) the item?", ref confResult) + ) + { + confResult = ConfirmationResult.NoToAll; + return false; + } + } + + if (confResult != ConfirmationResult.Yes && confResult != ConfirmationResult.YesToAll) + return false; //item is not to be deleted + + //if the model is new, it exists only locally wrapped in this ViewModel + //and all that is needed is to change its state + if (!this.IsModelNew && this.ModelRepository != null) + { + //the model is already in the persistent repository and must be deleted from it + try + { + this.ModelRepository.Delete(this.Model); + this.ModelRepository.Save(); //save all data + } + catch (Exception e) + { + DisplayNotification(NotificationType.Error, "Deletion failed", "Unable to delete the item.", e); + return false; + } + } + + this.ModelState = ModelStates.Deleted; + return true; + } + #endregion + + #region Model Validation + /// + /// Gets a value indicating whether the model is valid. + /// + /// + /// true if the model is valid; otherwise, false. + /// + public bool IsModelValid { + get + { + return ValidateModel() == String.Empty; + } + } + + /// + /// Validates the model. + /// + /// An empty string "", if the model is valid, error string otherwise. + internal virtual string ValidateModel(bool allProperties = true) + { + //validation based on IValidatableObject interface implementation of the model class and + //ValidationAttribute instances attached to both the model class type and all its properties + var valRes = new List(); + if (Validator.TryValidateObject(this.Model, + new ValidationContext(this.Model, null, null), //default validation context + valRes, allProperties) //validate also properties + ) + return String.Empty; //no error + + //there is some error, let us return the first one + return valRes.First().ErrorMessage; + } + + /// + /// Validates the given model property. + /// + /// Name of the property. + /// + /// An empty string "", if the property value is valid, error string otherwise. + /// + internal virtual string ValidateModelProperty(string propertyName) + { + //first, validate the property itself + var value = GetModelPropertyValue(propertyName); + var results = new List(1); + var result = Validator.TryValidateProperty(value, + new ValidationContext(this.Model) + { + + MemberName = propertyName + }, + results); + + string error = String.Empty; + if (!result) + { + //property value is invalid + var validationResult = results.First(); + error = validationResult.ErrorMessage; + } + + return error; + + } + + /// + /// Validates the model property identified by the given lambda expression selector. + /// + /// The property name selector, e.g. "() => PropertyName". + /// An empty string "", if the property value is valid, error string otherwise. + internal string ValidateModelProperty(Expression> propertyNameSelector) + { + return ValidateModelProperty(GetPropertyName(propertyNameSelector)); + } + + #endregion + + #region IDataErrorInfo Members + /// + /// Gets an error message indicating what is wrong with this object. + /// + /// An error message indicating what is wrong with this object. The default is an empty string (""). + string IDataErrorInfo.Error + { + get { return ValidateModel(false); } + } + + /// + /// Gets the error message for the property with the given name. + /// + /// Name of the column (property). + /// An empty string "", if the property value is valid, error string otherwise. + string IDataErrorInfo.this[string columnName] + { + get { return ValidateModelProperty(columnName); } + } + #endregion + } +} diff --git a/StudentEvaluatorCore/ViewModel/ViewModelBase.cs b/StudentEvaluatorCore/ViewModel/ViewModelBase.cs new file mode 100644 index 0000000..93e82cf --- /dev/null +++ b/StudentEvaluatorCore/ViewModel/ViewModelBase.cs @@ -0,0 +1,348 @@ +using System; +using System.Collections.Generic; +using System.ComponentModel; +using System.ComponentModel.DataAnnotations; +using System.Data.Entity.Validation; +using System.Diagnostics; +using System.Linq; +using System.Linq.Expressions; +using System.Reflection; +using System.Runtime.CompilerServices; +using System.Text; +using Zcu.StudentEvaluator.DAL; +using Zcu.StudentEvaluator.Model; +using Zcu.StudentEvaluator.View; + +namespace Zcu.StudentEvaluator.ViewModel +{ + /// + /// Base class for all ViewModel classes in the application. + /// It provides support for property change notifications and has a DisplayName property. This class is abstract. + /// + /// + /// ViewModel is a wrapper of Model and provides four kinds of properties: + /// a) Model Property = R/W property that provides direct R/W access to the same named property of the underlying model (e.g., FirstName) + /// b) Model Derived Property = R property that provides some product of values in model properties, e.g., FullName, DisplayName + /// c) Presentation Property = R/W property that stores some hints about how the ViewModel should be visualised, e.g., IsSelected + /// d) Another Property = special property, typically, private or protected + /// + /// All properties in a), b), c) groups notifies the listeners about changes done (via INotifyPropertyChanged). Properties in b) group + /// are being cached so that their product calculation is not necessary to repeat all the time. The cached value is released when + /// a change of such a property is notified (typically from some property in group a)). The cached is also released when + /// ResetModelCache is called (causes notification of changes, so that listeners may retrieve new values). Properties in a) group + /// are "cached" upon its first modification (as originalValues). When CancelModelChanges is called originalValues are copied to properties + /// (causes notification of changes). When SaveModelChanges is called, this "cache" is released, i.e., originalValues are released. + /// + ///INotifyPropertyChanged = standard interface for notifying listener that some property has changed. When WPF binding is being + ///established, the WPF core register itself as a listener of the source (an object implementing INotifyPropertyChanged) so as + ///whenever the source property changes, the underlying code passes the new value to the target property (using appropriate convertor) + ///IDataErrorInfo = standard interface for notifying caller about validation errors. When WPF binding is established + ///(with ValidatesOnDataErrors=True), after the value is passed from target property to the source property, + ///source object's IDataErrorInfo is used to get validation error. + /// + public abstract class ViewModelBase : IViewModelBase, INotifyPropertyChanged, IDisposable + { + #region Model Derived Properties Caching + private Dictionary _modelDerivedPropertiesCache = new Dictionary(); //cache for Model Derived Properties + + /// + /// Gets the model derived property value. + /// + /// return type + /// Name of the property. + /// Delegate that gives the original (default value). + /// The method returns the cached value of the property, or, if the value is not in the cache, it uses defaultSelector to + /// get the default value of the property, which is then stored into the cached and returned to the caller. + /// + /// The value of the property + /// + protected T GetModelDerivedPropertyValue([CallerMemberName] string propertyName = null, Func defaultSelector = null) + { + object value; + if (!_modelDerivedPropertiesCache.TryGetValue(propertyName, out value)) + { + value = (defaultSelector == null ? default(T) : defaultSelector()); + _modelDerivedPropertiesCache.Add(propertyName, value); + } + + return (T)value; + } + + /// + /// Gets the model derived property value. + /// + /// return type + /// Name of the property. + /// The default value. + /// The value of the property + protected T GetModelDerivedPropertyValue([CallerMemberName] string propertyName = null, T defaultValue = default(T)) + { + object value; + if (!_modelDerivedPropertiesCache.TryGetValue(propertyName, out value)) + _modelDerivedPropertiesCache.Add(propertyName, value = defaultValue); + + return (T)value; + } + + /// + /// Gets the value of the property selected by lambda expression. + /// + /// propertyNameSelector is supposed to be a lambda expression of style: () => PropertyName + /// return type + /// The property name selector. + /// The delegate to a function to be called when the cached value cannot be retrieved. + /// The value of the property + protected T GetModelDerivedPropertyValue(Expression> propertyNameSelector, Func defaultSelector = null) + { + return GetModelDerivedPropertyValue(GetPropertyName(propertyNameSelector), defaultSelector); + } + + /// + /// Gets the value of the property selected by lambda expression. + /// + /// return type + /// The property name selector. + /// The default value used when the cached value cannot be retrieved. + /// + /// The value of the property + /// + /// + /// propertyNameSelector is supposed to be a lambda expression of style: () => PropertyName + /// + protected T GetModelDerivedPropertyValue(Expression> propertyNameSelector, T defaultValue) + { + return GetModelDerivedPropertyValue(GetPropertyName(propertyNameSelector), defaultValue); + } + + /// + /// Clears all entries in the model derived properties cache. + /// + /// Raises change notification for every cached value. + protected void ClearModelDerivedPropertiesCache() + { + foreach (var item in _modelDerivedPropertiesCache.Keys.ToList()) //we need .ToList because we are going to modify the collection + { + RemoveModelDerivedPropertyCacheEntry(item); + } + } + + /// + /// Clears the entry in the model derived properties cache for the given property. + /// + /// Name of the property. + /// + /// Raises change notification for every cached value. + /// + protected void RemoveModelDerivedPropertyCacheEntry(string propertyName) + { + //make sure that listener that have the cached values are notified so that they may get fresh (new) values if necessary + if (_modelDerivedPropertiesCache.Remove(propertyName)) + NotifyPropertyChanged(propertyName); + } + + /// + /// Clears the entry in the model derived properties cache for the given property. + /// + /// The selector of the property. It is assumed that this selector is lambda expression + /// such as: "() => PropertyName". + /// Raises change notification for every cached value. + protected void RemoveModelDerivedPropertyCacheEntry(Expression> propertyNameSelector) + { + RemoveModelDerivedPropertyCacheEntry(GetPropertyName(propertyNameSelector)); + } + #endregion + + #region Representation Properties + /// + /// Returns the user-friendly name of this object. + /// Child classes can set this property to a new value, + /// or override it to determine the value on-demand. + /// + public virtual string DisplayName { get; protected set; } + + /// + /// Gets or sets a value indicating whether the ViewModel is read only. + /// + /// + /// true if the ViewModel is read only; otherwise, false. + /// + /// When IsReadOnly is set to true, setting any model property ends with an exception. + public bool IsReadOnly { get; protected set; } + #endregion // DisplayName + + #region DialogService Helpers + /// + /// Confirms the action to be done. + /// + /// Options available during the confirmation. + /// The caption, i.e., a short summary of what is needed to be confirmed. + /// The detailed explanation of what is to be confirmed. + /// The confirmation result. + /// true, if the confirmation dialog has been successfully done, false otherwise + protected bool ConfirmAction(ConfirmationOptions options, string caption, string message, ref ConfirmationResult confResult) + { + var confirmView = DialogService.DialogService.Default.Get(); + if (confirmView == null) + { + Debug.Fail("IConfirmationView could not be resolved."); + return false; + } + + confResult = confirmView.ConfirmAction(options, caption, message); + return true; + } + + /// + /// Displays the notification message to the user. + /// + /// The type of the notification. + /// The caption of the message, i.e., this is a short summary of what has happened. + /// The message to be displayed containing the detailed explanation of what has happened. + /// The exception containing all the details (may be null). + /// true, if the notification has been successfully displayed, false otherwise + protected bool DisplayNotification(NotificationType type, string caption, string message, Exception exc = null) + { + var notifyView = DialogService.DialogService.Default.Get(); + if (notifyView == null) + { + Debug.Fail("INotificationView could not be resolved."); + return false; + } + + notifyView.DisplayNotification(type, caption, message, exc); + return true; + } + #endregion + + #region Debugging Aides + /// + /// Warns the developer if this object does not have + /// a public property with the specified name. This + /// method does not exist in a Release build. + /// + [Conditional("DEBUG")] + [DebuggerStepThrough] + public void VerifyPropertyName(string propertyName) + { + // Verify that the property name matches a real, + // public, instance property on this object. + if (TypeDescriptor.GetProperties(this)[propertyName] == null) + { + var propInfo = this.GetType().GetProperty(propertyName, BindingFlags.Instance | BindingFlags.NonPublic); + if (propInfo == null || propInfo.GetAccessors(true)[0].IsPrivate) + { + string msg = "Invalid property name: " + propertyName; + + if (this.ThrowOnInvalidPropertyName) + throw new Exception(msg); + else + Debug.Fail(msg); + } + } + } + + /// + /// Returns whether an exception is thrown, or if a Debug.Fail() is used + /// when an invalid property name is passed to the VerifyPropertyName method. + /// The default value is false, but subclasses used by unit tests might + /// override this property's getter to return true. + /// + protected virtual bool ThrowOnInvalidPropertyName { get; private set; } + + #endregion // Debugging Aides + + #region INotifyPropertyChanged Members + + /// + /// Raised when a property on this object has a new value. + /// + public event PropertyChangedEventHandler PropertyChanged; + + /// + /// Raises this object's PropertyChanged event. + /// + /// The property that has a new value. + ///The CallerMemberName attribute that is applied to the optional propertyName (from .NET 4.5) + /// parameter causes the property name of the caller to be substituted as an argument. + protected virtual void NotifyPropertyChanged([CallerMemberName]string propertyName = "") + { + this.VerifyPropertyName(propertyName); + + PropertyChangedEventHandler handler = this.PropertyChanged; + if (handler != null) + { + var e = new PropertyChangedEventArgs(propertyName); + handler(this, e); + } + } + + /// + /// Raises this object's PropertyChanged event. + /// + /// Data type of the property that has changed + /// The selector of property name. + /// + /// This version has similar behaviour as [CallerMemberName] with the only reason that + /// the caller is not the property itself but some other method. This method is then called using lambda expression + /// as '() => Property' + /// + protected virtual void NotifyPropertyChanged(Expression> propertyNameSelector) + { + NotifyPropertyChanged(GetPropertyName(propertyNameSelector)); + } + + /// + /// Gets the name of the property passed as a lambda expression. + /// + /// Data type of the property that has changed + /// The property name selector. + /// + /// The name of the property or null, if propertyNameSelector does not selects a property + /// + protected string GetPropertyName(Expression> propertyNameSelector) + { + if (propertyNameSelector == null) + return null; + + var unary = propertyNameSelector.Body as UnaryExpression; //for value types, there is UnaryExpression (boxing) + var member = unary != null ? unary.Operand as MemberExpression : //for reference types, the member is already the property + propertyNameSelector.Body as MemberExpression; + + return member != null ? member.Member.Name : null; + } + + #endregion // INotifyPropertyChanged Members + + #region IDisposable Members + + /// + /// Invoked when this object is being removed from the application + /// and will be subject to garbage collection. + /// + public void Dispose() + { + this.OnDispose(); + } + + /// + /// Child classes can override this method to perform + /// clean-up logic, such as removing event handlers. + /// + protected virtual void OnDispose() + { + } + +#if DEBUG + /// + /// Useful for ensuring that ViewModel objects are properly garbage collected. + /// + ~ViewModelBase() + { + string msg = string.Format("{0} ({1}) ({2}) Finalized", this.GetType().Name, this.DisplayName, this.GetHashCode()); + System.Diagnostics.Debug.WriteLine(msg); + } +#endif + + #endregion // IDisposable Members + } +} diff --git a/StudentEvaluatorCore/packages.config b/StudentEvaluatorCore/packages.config new file mode 100644 index 0000000..0396cb0 --- /dev/null +++ b/StudentEvaluatorCore/packages.config @@ -0,0 +1,4 @@ + + + + \ No newline at end of file diff --git a/StudentEvaluatorCoreDesignData/DesignDataContext.cs b/StudentEvaluatorCoreDesignData/DesignDataContext.cs new file mode 100644 index 0000000..bfc1f00 --- /dev/null +++ b/StudentEvaluatorCoreDesignData/DesignDataContext.cs @@ -0,0 +1,26 @@ +using System; +using System.Collections.Generic; +using System.Linq; +using System.Text; +using System.Threading.Tasks; +using Zcu.StudentEvaluator.ViewModel; +using Zcu.StudentEvaluator.DAL; + +namespace Zcu.StudentEvaluator.DesignData +{ + public class DesignDataContext + { + public StudentListViewModel Students {get; private set;} + + public DesignDataContext() + { + var unitOfWork = new LocalStudentEvaluationUnitOfWork(); + unitOfWork.PopulateWithData(); //populate with default data + + this.Students = new StudentListViewModel(unitOfWork); + + this.Students.Items[0].IsSelected = true; + this.Students.Items[0].IsFocused = true; + } + } +} diff --git a/StudentEvaluatorCoreDesignData/Properties/AssemblyInfo.cs b/StudentEvaluatorCoreDesignData/Properties/AssemblyInfo.cs new file mode 100644 index 0000000..ae3224b --- /dev/null +++ b/StudentEvaluatorCoreDesignData/Properties/AssemblyInfo.cs @@ -0,0 +1,36 @@ +using System.Reflection; +using System.Runtime.CompilerServices; +using System.Runtime.InteropServices; + +// General Information about an assembly is controlled through the following +// set of attributes. Change these attribute values to modify the information +// associated with an assembly. +[assembly: AssemblyTitle("StudentEvaluatorCoreDesignData")] +[assembly: AssemblyDescription("")] +[assembly: AssemblyConfiguration("")] +[assembly: AssemblyCompany("")] +[assembly: AssemblyProduct("StudentEvaluatorCoreDesignData")] +[assembly: AssemblyCopyright("Copyright © 2014")] +[assembly: AssemblyTrademark("")] +[assembly: AssemblyCulture("")] + +// Setting ComVisible to false makes the types in this assembly not visible +// to COM components. If you need to access a type in this assembly from +// COM, set the ComVisible attribute to true on that type. +[assembly: ComVisible(false)] + +// The following GUID is for the ID of the typelib if this project is exposed to COM +[assembly: Guid("7e3717b7-a2d7-41b5-aa9f-bc68db6acbb1")] + +// Version information for an assembly consists of the following four values: +// +// Major Version +// Minor Version +// Build Number +// Revision +// +// You can specify all the values or you can default the Build and Revision Numbers +// by using the '*' as shown below: +// [assembly: AssemblyVersion("1.0.*")] +[assembly: AssemblyVersion("1.0.0.0")] +[assembly: AssemblyFileVersion("1.0.0.0")] diff --git a/StudentEvaluatorCoreDesignData/StudentEvaluatorCoreDesignData.csproj b/StudentEvaluatorCoreDesignData/StudentEvaluatorCoreDesignData.csproj new file mode 100644 index 0000000..845f045 --- /dev/null +++ b/StudentEvaluatorCoreDesignData/StudentEvaluatorCoreDesignData.csproj @@ -0,0 +1,59 @@ + + + + + Debug + AnyCPU + {8B39D65F-4373-45CF-B8E4-990FEE3BAC4F} + Library + Properties + StudentEvaluatorCoreDesignData + StudentEvaluatorCoreDesignData + v4.5 + 512 + + + true + full + false + bin\Debug\ + DEBUG;TRACE + prompt + 4 + + + pdbonly + true + bin\Release\ + TRACE + prompt + 4 + + + + + + + + + + + + + + + + + {5f535e59-c1a7-4766-86f1-bfbf71cb1b37} + StudentEvaluatorCore + + + + + \ No newline at end of file diff --git a/StudentEvaluatorCoreUnitTests/BaseUnitTest.cs b/StudentEvaluatorCoreUnitTests/BaseUnitTest.cs new file mode 100644 index 0000000..ebf1a19 --- /dev/null +++ b/StudentEvaluatorCoreUnitTests/BaseUnitTest.cs @@ -0,0 +1,27 @@ +using System; +using Microsoft.VisualStudio.TestTools.UnitTesting; +using Zcu.StudentEvaluator.DialogService; +using Zcu.StudentEvaluator.View; +using System.Diagnostics.CodeAnalysis; + +namespace StudentEvaluatorCoreUnitTests +{ + [TestClass] + [ExcludeFromCodeCoverage] + public class BaseUnitTest + { + private static ConfirmationView _confirmView = new ConfirmationView(); + private static NotificationView _notifyView = new NotificationView(); + + public ConfirmationView ConfirmView { get { return _confirmView; } } + public NotificationView NotifyView { get { return _notifyView; } } + + + [AssemblyInitialize] + public static void AssemblyInit(TestContext context) + { + DialogService.Default.RegisterSingleton(_confirmView, DialogConstants.ConfirmationView); + DialogService.Default.RegisterSingleton(_notifyView, DialogConstants.NotificationView); + } + } +} diff --git a/StudentEvaluatorCoreUnitTests/ConfirmationView.cs b/StudentEvaluatorCoreUnitTests/ConfirmationView.cs new file mode 100644 index 0000000..6938464 --- /dev/null +++ b/StudentEvaluatorCoreUnitTests/ConfirmationView.cs @@ -0,0 +1,27 @@ +using System; +using System.Collections.Generic; +using System.Diagnostics; +using System.Diagnostics.CodeAnalysis; +using System.Linq; +using System.Text; +using System.Threading.Tasks; +using Zcu.StudentEvaluator.View; + +namespace StudentEvaluatorCoreUnitTests +{ + [ExcludeFromCodeCoverage] + public class ConfirmationView : IConfirmationView + { + /// + /// Gets or sets the confirmation result to return automatically from ConfirmAction. + /// + public ConfirmationResult RequestedConfirmation { get; set; } + + public ConfirmationResult ConfirmAction(ConfirmationOptions options, string caption, string message) + { + Debug.WriteLine("CONFIRMATION REQUEST: {1} [{0}] ({2})", Enum.GetName(options.GetType(), options), caption, message); + Debug.WriteLine("CONFIRMATION RESPONSE: {0})", Enum.GetName(RequestedConfirmation.GetType(), RequestedConfirmation)); + return this.RequestedConfirmation; + } + } +} diff --git a/StudentEvaluatorCoreUnitTests/NotificationView.cs b/StudentEvaluatorCoreUnitTests/NotificationView.cs new file mode 100644 index 0000000..da7d009 --- /dev/null +++ b/StudentEvaluatorCoreUnitTests/NotificationView.cs @@ -0,0 +1,24 @@ +using System; +using System.Collections.Generic; +using System.Diagnostics; +using System.Diagnostics.CodeAnalysis; +using System.Linq; +using System.Text; +using System.Threading.Tasks; +using Zcu.StudentEvaluator.View; + +namespace StudentEvaluatorCoreUnitTests +{ + [ExcludeFromCodeCoverage] + public class NotificationView : INotificationView + { + public void DisplayNotification(NotificationType type, string caption, string message, Exception exc = null) + { + Debug.WriteLine("NOTIFICATION REQUEST: {0}-{1} ({2})", Enum.GetName(type.GetType(), type), caption, message); + if (exc != null) + { + Debug.WriteLine(exc); + } + } + } +} diff --git a/StudentEvaluatorCoreUnitTests/Properties/AssemblyInfo.cs b/StudentEvaluatorCoreUnitTests/Properties/AssemblyInfo.cs new file mode 100644 index 0000000..27b6b87 --- /dev/null +++ b/StudentEvaluatorCoreUnitTests/Properties/AssemblyInfo.cs @@ -0,0 +1,36 @@ +using System.Reflection; +using System.Runtime.CompilerServices; +using System.Runtime.InteropServices; + +// General Information about an assembly is controlled through the following +// set of attributes. Change these attribute values to modify the information +// associated with an assembly. +[assembly: AssemblyTitle("StudentEvaluatorCoreUnitTests")] +[assembly: AssemblyDescription("")] +[assembly: AssemblyConfiguration("")] +[assembly: AssemblyCompany("")] +[assembly: AssemblyProduct("StudentEvaluatorCoreUnitTests")] +[assembly: AssemblyCopyright("Copyright © 2013")] +[assembly: AssemblyTrademark("")] +[assembly: AssemblyCulture("")] + +// Setting ComVisible to false makes the types in this assembly not visible +// to COM components. If you need to access a type in this assembly from +// COM, set the ComVisible attribute to true on that type. +[assembly: ComVisible(false)] + +// The following GUID is for the ID of the typelib if this project is exposed to COM +[assembly: Guid("b4c00721-92ce-403a-acf6-74ed44d16f84")] + +// Version information for an assembly consists of the following four values: +// +// Major Version +// Minor Version +// Build Number +// Revision +// +// You can specify all the values or you can default the Build and Revision Numbers +// by using the '*' as shown below: +// [assembly: AssemblyVersion("1.0.*")] +[assembly: AssemblyVersion("1.0.0.0")] +[assembly: AssemblyFileVersion("1.0.0.0")] diff --git a/StudentEvaluatorCoreUnitTests/StudentEvaluatorCoreUnitTests.csproj b/StudentEvaluatorCoreUnitTests/StudentEvaluatorCoreUnitTests.csproj new file mode 100644 index 0000000..e72533a --- /dev/null +++ b/StudentEvaluatorCoreUnitTests/StudentEvaluatorCoreUnitTests.csproj @@ -0,0 +1,92 @@ + + + + Debug + AnyCPU + {706E6DD9-981E-4EBD-8445-82B3EF452862} + Library + Properties + StudentEvaluatorCoreUnitTests + StudentEvaluatorCoreUnitTests + v4.5 + 512 + {3AC096D0-A1C2-E12C-1390-A8335801FDAB};{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC} + 10.0 + $(MSBuildExtensionsPath32)\Microsoft\VisualStudio\v$(VisualStudioVersion) + $(ProgramFiles)\Common Files\microsoft shared\VSTT\$(VisualStudioVersion)\UITestExtensionPackages + False + UnitTest + + + true + full + false + bin\Debug\ + DEBUG;TRACE + prompt + 4 + + + pdbonly + true + bin\Release\ + TRACE + prompt + 4 + + + + + + + + + + + + + + + + + + + + + + + + + + {5f535e59-c1a7-4766-86f1-bfbf71cb1b37} + StudentEvaluatorCore + + + + + + + False + + + False + + + False + + + False + + + + + + + + \ No newline at end of file diff --git a/StudentEvaluatorCoreUnitTests/StudentViewModelUnitTest.cs b/StudentEvaluatorCoreUnitTests/StudentViewModelUnitTest.cs new file mode 100644 index 0000000..f067f6d --- /dev/null +++ b/StudentEvaluatorCoreUnitTests/StudentViewModelUnitTest.cs @@ -0,0 +1,309 @@ +using System; +using Microsoft.VisualStudio.TestTools.UnitTesting; +using Zcu.StudentEvaluator.DialogService; +using Zcu.StudentEvaluator.ViewModel; +using Zcu.StudentEvaluator.Model; +using System.Diagnostics.CodeAnalysis; +using Zcu.StudentEvaluator.View; + +namespace StudentEvaluatorCoreUnitTests +{ + [TestClass] + [ExcludeFromCodeCoverage] + public class StudentViewModelUnitTest : BaseUnitTest + { + public const string StudentPersonalNumber = "A12B34P"; + public const string StudentFirstName = "Anna"; + public const string StudentSurname = "Nova"; + + public static Student CreateStudentTestInstance() + { + return new Student() + { + PersonalNumber = StudentPersonalNumber, + FirstName = StudentFirstName, + Surname = StudentSurname, + }; + } + + [TestMethod] + public void TestStudentCtor() + { + var st = new Student(); + + Assert.IsNull(st.FirstName); + Assert.IsNull(st.PersonalNumber); + Assert.IsNull(st.Surname); + + Assert.IsNotNull(st.Evaluations); + + st = CreateStudentTestInstance(); + + Assert.AreEqual(StudentPersonalNumber, st.PersonalNumber); + Assert.AreEqual(StudentFirstName, st.FirstName); + Assert.AreEqual(StudentSurname, st.Surname); + } + + + [TestMethod] + public void TestConstructorWithNewModel() + { + var vm = new StudentViewModel(); + Assert.IsTrue(vm.IsModelNew); + Assert.IsFalse(vm.IsReadOnly, "New ViewModel cannot be in ReadOnly"); + } + + [TestMethod] + [ExpectedException(typeof(ArgumentNullException))] + public void TestConstructorWithNullModel() + { + var vm = new StudentViewModel((Student) null); + Assert.Fail("StudentViewModel should throw an exception when Model is null"); + } + + + [TestMethod] + public void TestConstructorWithExistingModel() + { + var vm = new StudentViewModel(CreateStudentTestInstance()); + Assert.IsFalse(vm.IsModelNew); + Assert.IsTrue(vm.IsModelValid); + Assert.IsFalse(vm.IsModelDirty); + } + + [TestMethod] + public void TestGetSetNotify() + { + int notificationsToSend = 0; + int notificationsSent = 0; + string propertyName = "FirstName"; + + var vm = new StudentViewModel(); + vm.PropertyChanged += (sender, e) => + { + if (e.PropertyName == propertyName) + notificationsSent++; + }; + + vm.FirstName = propertyName; + vm.FirstName = propertyName; //should be ignored since it is the same as previously + Assert.AreEqual(++notificationsToSend, notificationsSent, "PropertyChangedNotification not sent for " + propertyName); + Assert.AreEqual(vm.FirstName, propertyName); + Assert.IsFalse(vm.IsModelValid); + Assert.IsTrue(vm.IsModelDirty); + + propertyName = "Surname"; + vm.Surname = propertyName; + vm.Surname = propertyName; //should be ignored since it is the same as previously + Assert.AreEqual(++notificationsToSend, notificationsSent, "PropertyChangedNotification not sent for " + propertyName); + Assert.AreEqual(vm.Surname, propertyName); + Assert.IsFalse(vm.IsModelValid); + + propertyName = "PersonalNumber"; + vm.PersonalNumber = propertyName; + vm.PersonalNumber = propertyName;//should be ignored since it is the same as previously + Assert.AreEqual(++notificationsToSend, notificationsSent, "PropertyChangedNotification not sent for " + propertyName); + Assert.AreEqual(vm.PersonalNumber, propertyName); + Assert.IsFalse(vm.IsModelValid); + + ++notificationsToSend; + vm.PersonalNumber = "A12B02P"; + Assert.IsTrue(vm.IsModelValid); + Assert.IsTrue(vm.IsModelDirty); + + + propertyName = "IsSelected"; + Assert.IsFalse(vm.IsSelected); + vm.IsSelected = true; + vm.IsSelected = true; //should be ignored since it is the same as previously + Assert.AreEqual(++notificationsToSend, notificationsSent, "PropertyChangedNotification not sent for " + propertyName); + Assert.IsTrue(vm.IsSelected); + + propertyName = "IsFocused"; + Assert.IsFalse(vm.IsFocused); + vm.IsFocused = true; + vm.IsFocused = true; //should be ignored since it is the same as previously + Assert.AreEqual(++notificationsToSend, notificationsSent, "PropertyChangedNotification not sent for " + propertyName); + Assert.IsTrue(vm.IsFocused); + } + + [TestMethod] + [ExpectedException(typeof(InvalidOperationException))] + public void TestReadOnlyProtection() + { + var vm = new StudentViewModel(CreateStudentTestInstance()); + Assert.IsTrue(vm.IsReadOnly); + vm.Surname = "A"; + Assert.Fail(); + } + + [TestMethod] + public void TestEditCommand() + { + var vm = new StudentViewModel(CreateStudentTestInstance()); + Assert.IsTrue(vm.IsReadOnly); + Assert.IsNotNull(vm.EditCommand); + Assert.IsTrue(vm.EditCommand.CanExecute(null)); + + vm.EditCommand.Execute(null); + Assert.IsFalse(vm.IsReadOnly); + vm.Surname = "A"; + } + + [TestMethod] + public void TestGetDerivedProperties() + { + var vm = new StudentViewModel(CreateStudentTestInstance()); + Assert.AreEqual("NOVA Anna", vm.FullName); + + vm.EditCommand.Execute(null); + + int notificationsToSend = 0; + int notificationsSent = 0; + string propertyName = "FullName"; + vm.PropertyChanged += (sender, e) => + { + if (e.PropertyName == propertyName) + notificationsSent++; + }; + + vm.FirstName = null; + Assert.AreEqual("NOVA", vm.FullName); + Assert.AreEqual(++notificationsToSend, notificationsSent, + "PropertyChangedNotification not sent for " + propertyName); + + vm.Surname = null; + Assert.AreEqual(null, vm.FullName); + Assert.AreEqual(++notificationsToSend, notificationsSent, + "PropertyChangedNotification not sent for " + propertyName); + + vm.FirstName = StudentFirstName; + Assert.AreEqual(StudentFirstName, vm.FullName); + Assert.AreEqual(++notificationsToSend, notificationsSent, + "PropertyChangedNotification not sent for " + propertyName); + } + + [TestMethod] + [ExpectedException(typeof(NotImplementedException))] + public void TestCreateCommand() + { + var vm = new StudentViewModel(); + var command = vm.CreateCommand; + Assert.Fail(); + } + + class StudentViewModelTest : StudentViewModel + { + public StudentViewModelTest(Student model, ModelStates modelState) + : base(model, modelState) + { + + } + } + + [TestMethod] + public void TestProtectedCtor() + { + var vm = new StudentViewModelTest(CreateStudentTestInstance(), ViewModel.ModelStates.Updated); + Assert.IsTrue(vm.IsModelDirty); + Assert.IsFalse(vm.IsReadOnly); + + vm = new StudentViewModelTest(CreateStudentTestInstance(), ViewModel.ModelStates.Added); + Assert.IsTrue(vm.IsModelDirty); + Assert.IsFalse(vm.IsReadOnly); + + vm = new StudentViewModelTest(CreateStudentTestInstance(), ViewModel.ModelStates.Unchanged); + Assert.IsFalse(vm.IsModelDirty); + Assert.IsTrue(vm.IsReadOnly); + } + + [TestMethod] + [ExpectedException(typeof(ArgumentNullException))] + public void TestProtectedCtorWithNullModel() + { + var vm = new StudentViewModelTest(null, ViewModel.ModelStates.Updated); + Assert.Fail(); + } + + [TestMethod] + public void TestSaveCancelCommand() + { + var vm = new StudentViewModel(CreateStudentTestInstance()); + Assert.IsTrue(vm.IsReadOnly); + Assert.IsFalse(vm.SaveCommand.CanExecute(null)); + Assert.IsFalse(vm.CancelCommand.CanExecute(null)); + + vm.EditCommand.Execute(null); + Assert.IsFalse(vm.EditCommand.CanExecute(null)); //already in edit + vm.EditCommand.Execute(null); //this should have change nothing + + Assert.IsFalse(vm.SaveCommand.CanExecute(null)); + Assert.IsFalse(vm.CancelCommand.CanExecute(null)); + Assert.IsFalse(vm.IsModelDirty); + + vm.SaveCommand.Execute(null); //this should be ignored since CanExecute is false + vm.CancelCommand.Execute(null); //this should be ignored since CanExecute is false + Assert.IsFalse(vm.IsReadOnly); + + vm.FirstName = "K"; + + Assert.IsTrue(vm.IsModelDirty); + Assert.IsTrue(vm.SaveCommand.CanExecute(null)); + Assert.IsTrue(vm.CancelCommand.CanExecute(null)); + + vm.CancelCommand.Execute(null); + + Assert.IsFalse(vm.IsModelDirty); + Assert.IsTrue(vm.IsReadOnly); + Assert.IsFalse(vm.SaveCommand.CanExecute(null)); + Assert.IsFalse(vm.CancelCommand.CanExecute(null)); + Assert.AreEqual(vm.FirstName, StudentFirstName); + + vm.EditCommand.Execute(null); + vm.FirstName = "K"; + vm.SaveCommand.Execute(null); + + Assert.IsFalse(vm.IsModelDirty); + Assert.IsTrue(vm.IsReadOnly); + Assert.IsFalse(vm.SaveCommand.CanExecute(null)); + Assert.IsFalse(vm.CancelCommand.CanExecute(null)); + Assert.AreEqual(vm.FirstName, "K"); + + vm = new StudentViewModelTest(CreateStudentTestInstance(), ViewModel.ModelStates.Added); + vm.SaveCommand.Execute(null); + Assert.IsTrue(vm.IsReadOnly); //saved + } + + [TestMethod] + public void TestDeleteCommand() + { + var vm = new StudentViewModel(CreateStudentTestInstance()); + Assert.IsFalse(vm.IsModelDeleted); + + ConfirmView.RequestedConfirmation = ConfirmationResult.No; + + Assert.IsTrue(vm.DeleteCommand.CanExecute(null)); + vm.DeleteCommand.Execute(null); + Assert.IsFalse(vm.IsModelDeleted); + + ConfirmView.RequestedConfirmation = ConfirmationResult.Yes; + vm.DeleteCommand.Execute(null); + Assert.IsTrue(vm.IsModelDeleted); + + Assert.IsFalse(vm.DeleteCommand.CanExecute(null)); + Assert.IsFalse(vm.EditCommand.CanExecute(null)); + Assert.IsFalse(vm.SaveCommand.CanExecute(null)); + Assert.IsFalse(vm.CancelCommand.CanExecute(null)); + + vm.DeleteCommand.Execute(null); //should do nothing + + vm = new StudentViewModel(); + vm.DeleteCommand.Execute(null); + Assert.IsFalse(vm.IsModelDeleted); + + vm = new StudentViewModelTest(new Student(), ViewModel.ModelStates.Unchanged); + vm.DeleteCommand.Execute(null); //PersonalNumber is null + Assert.IsTrue(vm.IsModelDeleted); + } + } +} diff --git a/StudentEvaluatorWPFApp/App.config b/StudentEvaluatorWPFApp/App.config new file mode 100644 index 0000000..8ea5b3d --- /dev/null +++ b/StudentEvaluatorWPFApp/App.config @@ -0,0 +1,42 @@ + + + + +
+ + +
+ + + + + + + + + + + #FF006400 + + + #FF4682B4 + + + #FFFFA500 + + + #FFFF0000 + + + + + + + + + + + + + + \ No newline at end of file diff --git a/StudentEvaluatorWPFApp/App.xaml b/StudentEvaluatorWPFApp/App.xaml new file mode 100644 index 0000000..1024213 --- /dev/null +++ b/StudentEvaluatorWPFApp/App.xaml @@ -0,0 +1,8 @@ + + + + + diff --git a/StudentEvaluatorWPFApp/App.xaml.cs b/StudentEvaluatorWPFApp/App.xaml.cs new file mode 100644 index 0000000..69b49be --- /dev/null +++ b/StudentEvaluatorWPFApp/App.xaml.cs @@ -0,0 +1,103 @@ +using System; +using System.Collections.Generic; +using System.Configuration; +using System.Data; +using System.Data.Entity.Validation; +using System.Linq; +using System.Text; +using System.Threading; +using System.Threading.Tasks; +using System.Windows; +using System.Windows.Input; +using Zcu.StudentEvaluator.DAL; +using Zcu.StudentEvaluator.DesktopApp.View; +using Zcu.StudentEvaluator.View; +using Zcu.StudentEvaluator.ViewModel; + +namespace Zcu.StudentEvaluator.DesktopApp +{ + /// + /// Interaction logic for App.xaml + /// + public partial class App : Application + { + private DbStudentEvaluationUnitOfWork _unitOfWork = null; + + /// + /// Raises the event. + /// + /// A that contains the event data. + protected override void OnStartup(StartupEventArgs e) + { + // Thread.CurrentThread.CurrentUICulture = new System.Globalization.CultureInfo("cs-CZ"); + Thread.CurrentThread.CurrentUICulture = new System.Globalization.CultureInfo("en-US"); + + //RelayCommand Injector + RelayCommandInjector.CanExecuteChangedAddAction = new Action((value) => + { + CommandManager.RequerySuggested += (System.EventHandler)value; + }); + + RelayCommandInjector.CanExecuteChangedRemoveAction = new Action((value) => + { + CommandManager.RequerySuggested -= (System.EventHandler)value; + }); + + RelayCommandInjector.RaiseCanExecuteChangedAction = new Action((value) => + { + CommandManager.InvalidateRequerySuggested(); + }); + + + //Register Views + DialogService.DialogService.Default.Register(DialogService.DialogConstants.ConfirmationView); + DialogService.DialogService.Default.Register(DialogService.DialogConstants.EditStudentView); + + //DialogService.DialogService.Default.Register(); //main View + + + _unitOfWork = + //new LocalStudentEvaluationUnitOfWork(); + new DbStudentEvaluationUnitOfWork(); + if (_unitOfWork.Categories.Get().FirstOrDefault() == null) + { + try + { + _unitOfWork.PopulateWithData(); + } + catch (DbEntityValidationException excValidation) + { + var sb = new StringBuilder(); + + foreach (var item in excValidation.EntityValidationErrors) + { + sb.AppendFormat("Validation of '{0}' failed with these errors:\n", item.Entry.Entity.GetType().Name); + foreach (var err in item.ValidationErrors) + { + sb.AppendFormat("For '{0}' : {1}\n", err.PropertyName, err.ErrorMessage); + } + } + + MessageBox.Show(sb.ToString()); + } + } + + + base.OnStartup(e); + } + + /// + /// Raises the event. + /// + /// An that contains the event data. + protected override void OnActivated(EventArgs e) + { + if (this.MainWindow != null && this.MainWindow.DataContext == null) + { + this.MainWindow.DataContext = new StudentListViewModel(this._unitOfWork); + } + + base.OnActivated(e); + } + } +} diff --git a/StudentEvaluatorWPFApp/Design/DesignData.cs b/StudentEvaluatorWPFApp/Design/DesignData.cs new file mode 100644 index 0000000..0bf882e --- /dev/null +++ b/StudentEvaluatorWPFApp/Design/DesignData.cs @@ -0,0 +1,18 @@ +using System; +using System.Collections.Generic; +using System.Linq; +using System.Text; +using System.Threading.Tasks; +using Zcu.StudentEvaluator.DAL; + +namespace Zcu.StudentEvaluator.ViewModel.Design +{ + internal class DesignData + { + public DesignData() + { + var unitOfWork = new LocalStudentEvaluationUnitOfWork(); + unitOfWork.PopulateWithData(); + } + } +} diff --git a/StudentEvaluatorWPFApp/MainWindow.xaml b/StudentEvaluatorWPFApp/MainWindow.xaml new file mode 100644 index 0000000..c61d0af --- /dev/null +++ b/StudentEvaluatorWPFApp/MainWindow.xaml @@ -0,0 +1,25 @@ + + + + + + + + + + + + + diff --git a/StudentEvaluatorWPFApp/MainWindow.xaml.cs b/StudentEvaluatorWPFApp/MainWindow.xaml.cs new file mode 100644 index 0000000..65e3efd --- /dev/null +++ b/StudentEvaluatorWPFApp/MainWindow.xaml.cs @@ -0,0 +1,38 @@ +using System; +using System.Collections.Generic; +using System.Linq; +using System.Text; +using System.Threading.Tasks; +using System.Windows; +using System.Windows.Controls; +using System.Windows.Data; +using System.Windows.Documents; +using System.Windows.Input; +using System.Windows.Media; +using System.Windows.Media.Imaging; +using System.Windows.Navigation; +using System.Windows.Shapes; +using Zcu.StudentEvaluator.View; + +namespace Zcu.StudentEvaluator.DesktopApp +{ + /// + /// Interaction logic for MainWindow.xaml + /// + public partial class MainWindow : Window + { + public MainWindow() + { + InitializeComponent(); + InitializeIOC(); + } + + /// + /// Initializes the Interface / Window. + /// + private void InitializeIOC() + { + DialogService.DialogService.Default.RegisterSingleton(this.NtfView, DialogService.DialogConstants.NotificationView); + } + } +} diff --git a/StudentEvaluatorWPFApp/Properties/AssemblyInfo.cs b/StudentEvaluatorWPFApp/Properties/AssemblyInfo.cs new file mode 100644 index 0000000..e4485cd --- /dev/null +++ b/StudentEvaluatorWPFApp/Properties/AssemblyInfo.cs @@ -0,0 +1,55 @@ +using System.Reflection; +using System.Resources; +using System.Runtime.CompilerServices; +using System.Runtime.InteropServices; +using System.Windows; + +// General Information about an assembly is controlled through the following +// set of attributes. Change these attribute values to modify the information +// associated with an assembly. +[assembly: AssemblyTitle("StudentEvaluatorWPFApp")] +[assembly: AssemblyDescription("")] +[assembly: AssemblyConfiguration("")] +[assembly: AssemblyCompany("")] +[assembly: AssemblyProduct("StudentEvaluatorWPFApp")] +[assembly: AssemblyCopyright("Copyright © 2014")] +[assembly: AssemblyTrademark("")] +[assembly: AssemblyCulture("")] + +// Setting ComVisible to false makes the types in this assembly not visible +// to COM components. If you need to access a type in this assembly from +// COM, set the ComVisible attribute to true on that type. +[assembly: ComVisible(false)] + +//In order to begin building localizable applications, set +//CultureYouAreCodingWith in your .csproj file +//inside a . For example, if you are using US english +//in your source files, set the to en-US. Then uncomment +//the NeutralResourceLanguage attribute below. Update the "en-US" in +//the line below to match the UICulture setting in the project file. + +//[assembly: NeutralResourcesLanguage("en-US", UltimateResourceFallbackLocation.Satellite)] + + +[assembly: ThemeInfo( + ResourceDictionaryLocation.None, //where theme specific resource dictionaries are located + //(used if a resource is not found in the page, + // or application resource dictionaries) + ResourceDictionaryLocation.SourceAssembly //where the generic resource dictionary is located + //(used if a resource is not found in the page, + // app, or any theme specific resource dictionaries) +)] + + +// Version information for an assembly consists of the following four values: +// +// Major Version +// Minor Version +// Build Number +// Revision +// +// You can specify all the values or you can default the Build and Revision Numbers +// by using the '*' as shown below: +// [assembly: AssemblyVersion("1.0.*")] +[assembly: AssemblyVersion("1.0.0.0")] +[assembly: AssemblyFileVersion("1.0.0.0")] diff --git a/StudentEvaluatorWPFApp/Properties/ColorSettings.Designer.cs b/StudentEvaluatorWPFApp/Properties/ColorSettings.Designer.cs new file mode 100644 index 0000000..5aad06e --- /dev/null +++ b/StudentEvaluatorWPFApp/Properties/ColorSettings.Designer.cs @@ -0,0 +1,74 @@ +//------------------------------------------------------------------------------ +// +// This code was generated by a tool. +// Runtime Version:4.0.30319.34011 +// +// Changes to this file may cause incorrect behavior and will be lost if +// the code is regenerated. +// +//------------------------------------------------------------------------------ + +namespace Zcu.StudentEvaluator.Properties { + + + [global::System.Runtime.CompilerServices.CompilerGeneratedAttribute()] + [global::System.CodeDom.Compiler.GeneratedCodeAttribute("Microsoft.VisualStudio.Editors.SettingsDesigner.SettingsSingleFileGenerator", "11.0.0.0")] + internal sealed partial class ColorSettings : global::System.Configuration.ApplicationSettingsBase { + + private static ColorSettings defaultInstance = ((ColorSettings)(global::System.Configuration.ApplicationSettingsBase.Synchronized(new ColorSettings()))); + + public static ColorSettings Default { + get { + return defaultInstance; + } + } + + [global::System.Configuration.UserScopedSettingAttribute()] + [global::System.Diagnostics.DebuggerNonUserCodeAttribute()] + [global::System.Configuration.DefaultSettingValueAttribute("#FF006400")] + public global::System.Windows.Media.Color MessageColor { + get { + return ((global::System.Windows.Media.Color)(this["MessageColor"])); + } + set { + this["MessageColor"] = value; + } + } + + [global::System.Configuration.UserScopedSettingAttribute()] + [global::System.Diagnostics.DebuggerNonUserCodeAttribute()] + [global::System.Configuration.DefaultSettingValueAttribute("#FF4682B4")] + public global::System.Windows.Media.Color SelectionColor { + get { + return ((global::System.Windows.Media.Color)(this["SelectionColor"])); + } + set { + this["SelectionColor"] = value; + } + } + + [global::System.Configuration.UserScopedSettingAttribute()] + [global::System.Diagnostics.DebuggerNonUserCodeAttribute()] + [global::System.Configuration.DefaultSettingValueAttribute("#FFFFA500")] + public global::System.Windows.Media.Color WarningColor { + get { + return ((global::System.Windows.Media.Color)(this["WarningColor"])); + } + set { + this["WarningColor"] = value; + } + } + + [global::System.Configuration.UserScopedSettingAttribute()] + [global::System.Diagnostics.DebuggerNonUserCodeAttribute()] + [global::System.Configuration.DefaultSettingValueAttribute("#FFFF0000")] + public global::System.Windows.Media.Color ErrorColor { + get { + return ((global::System.Windows.Media.Color)(this["ErrorColor"])); + } + set { + this["ErrorColor"] = value; + } + } + } +} diff --git a/StudentEvaluatorWPFApp/Properties/ColorSettings.settings b/StudentEvaluatorWPFApp/Properties/ColorSettings.settings new file mode 100644 index 0000000..d6016aa --- /dev/null +++ b/StudentEvaluatorWPFApp/Properties/ColorSettings.settings @@ -0,0 +1,18 @@ + + + + + + #FF006400 + + + #FF4682B4 + + + #FFFFA500 + + + #FFFF0000 + + + \ No newline at end of file diff --git a/StudentEvaluatorWPFApp/Properties/Resources.Designer.cs b/StudentEvaluatorWPFApp/Properties/Resources.Designer.cs new file mode 100644 index 0000000..4924493 --- /dev/null +++ b/StudentEvaluatorWPFApp/Properties/Resources.Designer.cs @@ -0,0 +1,63 @@ +//------------------------------------------------------------------------------ +// +// This code was generated by a tool. +// Runtime Version:4.0.30319.34011 +// +// Changes to this file may cause incorrect behavior and will be lost if +// the code is regenerated. +// +//------------------------------------------------------------------------------ + +namespace Zcu.StudentEvaluator.DesktopApp.Properties { + using System; + + + /// + /// A strongly-typed resource class, for looking up localized strings, etc. + /// + // This class was auto-generated by the StronglyTypedResourceBuilder + // class via a tool like ResGen or Visual Studio. + // To add or remove a member, edit your .ResX file then rerun ResGen + // with the /str option, or rebuild your VS project. + [global::System.CodeDom.Compiler.GeneratedCodeAttribute("System.Resources.Tools.StronglyTypedResourceBuilder", "4.0.0.0")] + [global::System.Diagnostics.DebuggerNonUserCodeAttribute()] + [global::System.Runtime.CompilerServices.CompilerGeneratedAttribute()] + internal class Resources { + + private static global::System.Resources.ResourceManager resourceMan; + + private static global::System.Globalization.CultureInfo resourceCulture; + + [global::System.Diagnostics.CodeAnalysis.SuppressMessageAttribute("Microsoft.Performance", "CA1811:AvoidUncalledPrivateCode")] + internal Resources() { + } + + /// + /// Returns the cached ResourceManager instance used by this class. + /// + [global::System.ComponentModel.EditorBrowsableAttribute(global::System.ComponentModel.EditorBrowsableState.Advanced)] + internal static global::System.Resources.ResourceManager ResourceManager { + get { + if (object.ReferenceEquals(resourceMan, null)) { + global::System.Resources.ResourceManager temp = new global::System.Resources.ResourceManager("Zcu.StudentEvaluator.DesktopApp.Properties.Resources", typeof(Resources).Assembly); + resourceMan = temp; + } + return resourceMan; + } + } + + /// + /// Overrides the current thread's CurrentUICulture property for all + /// resource lookups using this strongly typed resource class. + /// + [global::System.ComponentModel.EditorBrowsableAttribute(global::System.ComponentModel.EditorBrowsableState.Advanced)] + internal static global::System.Globalization.CultureInfo Culture { + get { + return resourceCulture; + } + set { + resourceCulture = value; + } + } + } +} diff --git a/StudentEvaluatorWPFApp/Properties/Resources.resx b/StudentEvaluatorWPFApp/Properties/Resources.resx new file mode 100644 index 0000000..af7dbeb --- /dev/null +++ b/StudentEvaluatorWPFApp/Properties/Resources.resx @@ -0,0 +1,117 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + text/microsoft-resx + + + 2.0 + + + System.Resources.ResXResourceReader, System.Windows.Forms, Version=2.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089 + + + System.Resources.ResXResourceWriter, System.Windows.Forms, Version=2.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089 + + \ No newline at end of file diff --git a/StudentEvaluatorWPFApp/Properties/Settings.Designer.cs b/StudentEvaluatorWPFApp/Properties/Settings.Designer.cs new file mode 100644 index 0000000..5d09832 --- /dev/null +++ b/StudentEvaluatorWPFApp/Properties/Settings.Designer.cs @@ -0,0 +1,26 @@ +//------------------------------------------------------------------------------ +// +// This code was generated by a tool. +// Runtime Version:4.0.30319.34011 +// +// Changes to this file may cause incorrect behavior and will be lost if +// the code is regenerated. +// +//------------------------------------------------------------------------------ + +namespace Zcu.StudentEvaluator.DesktopApp.Properties { + + + [global::System.Runtime.CompilerServices.CompilerGeneratedAttribute()] + [global::System.CodeDom.Compiler.GeneratedCodeAttribute("Microsoft.VisualStudio.Editors.SettingsDesigner.SettingsSingleFileGenerator", "11.0.0.0")] + internal sealed partial class Settings : global::System.Configuration.ApplicationSettingsBase { + + private static Settings defaultInstance = ((Settings)(global::System.Configuration.ApplicationSettingsBase.Synchronized(new Settings()))); + + public static Settings Default { + get { + return defaultInstance; + } + } + } +} diff --git a/StudentEvaluatorWPFApp/Properties/Settings.settings b/StudentEvaluatorWPFApp/Properties/Settings.settings new file mode 100644 index 0000000..033d7a5 --- /dev/null +++ b/StudentEvaluatorWPFApp/Properties/Settings.settings @@ -0,0 +1,7 @@ + + + + + + + \ No newline at end of file diff --git a/StudentEvaluatorWPFApp/StudentEvaluatorWPFApp.csproj b/StudentEvaluatorWPFApp/StudentEvaluatorWPFApp.csproj new file mode 100644 index 0000000..a8c97c1 --- /dev/null +++ b/StudentEvaluatorWPFApp/StudentEvaluatorWPFApp.csproj @@ -0,0 +1,171 @@ + + + + + Debug + AnyCPU + {1EA3CEFB-0CE3-4E04-A0E5-FB2A38BA4F54} + WinExe + Properties + Zcu.StudentEvaluator.DesktopApp + StudentEvaluatorWPFApp + v4.5 + 512 + {60dc8134-eba5-43b8-bcc9-bb4bc16c2548};{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC} + 4 + + + AnyCPU + true + full + false + bin\Debug\ + DEBUG;TRACE + prompt + 4 + + + AnyCPU + pdbonly + true + bin\Release\ + TRACE + prompt + 4 + + + + False + ..\packages\EntityFramework.6.0.2\lib\net45\EntityFramework.dll + + + ..\packages\EntityFramework.6.0.2\lib\net45\EntityFramework.SqlServer.dll + + + + + + + + + + + + 4.0 + + + + + + + + MSBuild:Compile + Designer + + + + ColorSettings.settings + True + True + + + ConfirmationView.xaml + + + NotificationView.xaml + + + StudentListView.xaml + + + StudentListViewItem.xaml + + + StudentView.xaml + + + MSBuild:Compile + Designer + + + App.xaml + Code + + + MainWindow.xaml + Code + + + Designer + MSBuild:Compile + Zcu.StudentEvaluator.View + + + Designer + MSBuild:Compile + + + Designer + MSBuild:Compile + + + Designer + MSBuild:Compile + + + Designer + MSBuild:Compile + + + + + Code + + + True + True + Resources.resx + + + True + Settings.settings + True + + + ResXFileCodeGenerator + Resources.Designer.cs + + + + SettingsSingleFileGenerator + ColorSettings.Designer.cs + Zcu.StudentEvaluator.Properties + + + SettingsSingleFileGenerator + Settings.Designer.cs + + + + + + + + + {8b39d65f-4373-45cf-b8e4-990fee3bac4f} + StudentEvaluatorCoreDesignData + + + {5f535e59-c1a7-4766-86f1-bfbf71cb1b37} + StudentEvaluatorCore + + + + + \ No newline at end of file diff --git a/StudentEvaluatorWPFApp/View/ConfirmationView.xaml b/StudentEvaluatorWPFApp/View/ConfirmationView.xaml new file mode 100644 index 0000000..abeb7b0 --- /dev/null +++ b/StudentEvaluatorWPFApp/View/ConfirmationView.xaml @@ -0,0 +1,25 @@ + + + + + + + + + + +