I'm using SPMetal in order to generate entity classes for my sharepoint site and I'm not exactly sure what the best practice is to use when there are multiple content types for a single list. For instance I have a task list that contains 2 content types and I'm defining them via the config file for SPMetal. Here is my definition...
<List Member="Tasks" Name="Tasks">
<ContentType Class="LegalReview" Name="LegalReviewContent"/>
<ContentType Class="Approval" Name="ApprovalContent"/>
</List>
This seems to work pretty well in that the generated objects do inherit from WorkflowTask but the generated type for the data context is a List of WorkflowTask. So when I do a query I get back a WorkflowTask object instead of a LegalReview or Approval object. How do I make it return an object of the correct type?
[Microsoft.SharePoint.Linq.ListAttribute(Name="Tasks")]
public Microsoft.SharePoint.Linq.EntityList<WorkflowTask> Tasks {
get {
return this.GetList<WorkflowTask>("Tasks");
}
}
UPDATE
Thanks for getting back to me. I'm not sure how I recreate the type based on the SPListItem and would appreciate any feedback.
ContractManagementDataContext context = new ContractManagementDataContext(_url);
WorkflowTask task = context.Tasks.FirstOrDefault(t => t.Id ==5);
Approval a = new Approval(task.item);
public partial class Approval{
public Approval(SPListItem item){
//Set all properties here for workflowtask and approval type?
//Wouldn't there be issues since it isn't attached to the datacontext?
}
public String SomeProperty{
get{ //get from list item};
set{ //set to list item};
}
Linq2SharePoint will always return an object of the first common base ContentType for all the ContentTypes in the list. This is not only because a base type of some description must be used to combine the different ContentTypes in code but also it will then only map the fields that should definitely exist on all ContentTypes in the list. It is however possible to get access to the underlying SPListItem returned by L2SP and thus from that determine the ContentType and down cast the item.
As part of a custom repository layer that is generated from T4 templates we have a partial addition to the Item class generated by SPMetal which implements ICustomMapping to get the data not usually available on the L2SP entities. A simplified version is below which just gets the ContentType and ModifiedDate to show the methodology; though the full class we use also maps Modified By, Created Date/By, Attachments, Version, Path etc, the principle is the same for all.
public partial class Item : ICustomMapping
{
private SPListItem _SPListItem;
public SPListItem SPListItem
{
get { return _SPListItem; }
set { _SPListItem = value; }
}
public string ContentTypeId { get; internal set; }
public DateTime Modified { get; internal set; }
public virtual void MapFrom(object listItem)
{
SPListItem item = (SPListItem)listItem;
this.SPListItem = item;
this.ContentTypeId = item.ContentTypeId.ToString();
this.Modified = (DateTime)item["Modified"];
}
public virtual void MapTo(object listItem)
{
SPListItem item = (SPListItem)listItem;
item["Modified"] = this.Modified == DateTime.MinValue ? this.Modified = DateTime.Now : this.Modified;
}
public virtual void Resolve(RefreshMode mode, object originalListItem, object databaseObject)
{
SPListItem originalItem = (SPListItem)originalListItem;
SPListItem databaseItem = (SPListItem)databaseObject;
DateTime originalModifiedValue = (DateTime)originalItem["Modified"];
DateTime dbModifiedValue = (DateTime)databaseItem["Modified"];
string originalContentTypeIdValue = originalItem.ContentTypeId.ToString();
string dbContentTypeIdValue = databaseItem.ContentTypeId.ToString();
switch(mode)
{
case RefreshMode.OverwriteCurrentValues:
this.Modified = dbModifiedValue;
this.ContentTypeId = dbContentTypeIdValue;
break;
case RefreshMode.KeepCurrentValues:
databaseItem["Modified"] = this.Modified;
break;
case RefreshMode.KeepChanges:
if (this.Modified != originalModifiedValue)
{
databaseItem["Modified"] = this.Modified;
}
else if (this.Modified == originalModifiedValue && this.Modified != dbModifiedValue)
{
this.Modified = dbModifiedValue;
}
if (this.ContentTypeId != originalContentTypeIdValue)
{
throw new InvalidOperationException("You cannot change the ContentTypeId directly");
}
else if (this.ContentTypeId == originalContentTypeIdValue && this.ContentTypeId != dbContentTypeIdValue)
{
this.ContentTypeId = dbContentTypeIdValue;
}
break;
}
}
}
Once you have the ContentType and the underlying SPListItem available on your L2SP entity it is simply a matter of writing a method which returns an instance of the derived ContentType entity from a combination of the values of the base type and the extra data for the missing fields from the SPListItem.
UPDATE: I don't actually have an example converter class as we don't use the above mapping extension to Item in this way. However I could imagine something like this would work:
public static class EntityConverter
{
public static Approval ToApproval(WorkflowTask wft)
{
Approval a = new Approval();
a.SomePropertyOnWorkflowTask = wft.SomePropertyOnWorkflowTask;
a.SomePropertyOnApproval = wft.SPListItem["field-name"];
return a;
}
}
Or you could put a method on a partial instance of WorkflowTask to return an Approval object.
public partial class WorkflowTask
{
public Approval ToApproval()
{
Approval a = new Approval();
a.SomePropertyOnWorkflowTask = this.SomePropertyOnWorkflowTask;
a.SomePropertyOnApproval = this.SPListItem["field-name"];
return a;
}
public LegalReview ToLegalReview()
{
// Create and return LegalReview as for Approval
}
}
In either situation you would need to determine the method to call to get the derived type from the ContentTypeId property of the WorkflowTask. This is the sort of code I would normally want to generate in one form or another as it will be pretty repetitive but that is a bit off-topic.
Consider, for example's sake, the logic "A user may only edit or delete a comment that the user has authored".
My Controller Actions will repeat the logic of checking whether the currently logged in user can affect the comment. Example
[Authorize]
public ActionResult DeleteComment(int comment_id)
{
var comment = CommentsRepository.getCommentById(comment_id);
if(comment == null)
// Cannot find comment, return bad input
return new HttpStatusCodeResult(400);
if(comment.author != User.Identity.Name)
// User not allowed to delete this comment, return Forbidden
return new HttpStatusCodeResult(403);
// Error checking passed, continue with delete action
return new HttpStatusCodeResult(200);
}
Of course, I can bundle that logic up in a method so that I'm not copy / pasting that snippet; however, taking that code out of the controller and putting it in a ValidationAttribute keeps my Action smaller and easier to write tests for. Example
public class MustBeCommentAuthorAttribute : ValidationAttribute
{
// Import attribute for Dependency Injection
[Import]
ICommentRepository CommentRepository { get; set; }
protected override ValidationResult IsValid(object value, ValidationContext validationContext)
{
int comment_id = (int)value;
var comment = CommentsRepository.getCommentById(comment_id);
if(comment == null)
return new ValidationResult("No comment with that ID");
if(comment.author != HttpContext.Current.User.Identity.Name)
return new ValidationResult("Cannot edit this comment");
// No errors
return ValidationResult.Success;
}
}
public class DeleteCommentModel
{
[MustBeCommentAuthor]
public int comment_id { get; set; }
}
Is Model Validation the right tool for this job? I like taking that concern out of the controller Action; but in this case, it may complicate things further. This is especially true when you consider that this Action is part of a RESTful API and needs to return a different HTTP Status Code depending on the Validation errors in the ModelState.
Is there "best practice" in this case?
Personally, I think that it looks nice, but you are getting carried away with annotations. I think that this does not belong in your presentation layer and it should be handled by your service layer.
I would have something on the lines of:
[Authorize]
public ActionResult DeleteComment(int comment_id)
{
try
{
var result = CommentsService.GetComment(comment_id, Auth.Username);
// Show success to the user
}
catch(Exception e)
{
// Handle by displaying relevant message to the user
}
}
I was hoping there was an easier way to do this in my MVC 3 project. In my database, I have a Customer table that is mapped in my application via LINQ2SQL. There is also a partial customer class where I perform updates, look-up etc - which where I have an update method like this:
public static void Update(Customer customer)
{
if (customer == null)
return;
using(var db = new DataBaseContext)
{
var newCustomer = db.Customers.Where(c => c.customer_id = customer.customer_id).SingleOrDefault();
if(newCustomer == null)
return;
newCustomer.first_nm = customer.first_nm;
// ...
// ... Lot's of fields to update
// ...
newCustomer.phone_num = customer.phone_nm;
db.SubmitChanges();
}
}
What I was hoping to find was a less-cumbersome method to update the fields in newCustomer with the corresponding fields in customer that are different.
Any suggestions? Thanks.
I think you can implement IEqualityComparer:
public class Customer
{
public string first_nm { get; set; }
public int phone_num { get; set; }
}
class CustomerComparer : IEqualityComparer<Customer>
{
public bool Equals(Customer x, Customer y)
{
//Check whether the compared objects reference the same data.
if (Object.ReferenceEquals(x, y)) return true;
//Check whether any of the compared objects is null.
if (Object.ReferenceEquals(x, null) || Object.ReferenceEquals(y, null))
return false;
//Check whether the customer' properties are equal.
return x.first_nm == y.first_nm && x.phone_num == y.phone_num ;
}
}
and do it as follows:
if (newCustomer != customer)
{
myDbContext.Customers.Attach(customer,true); // true means modified.
}
Or implement ICloneable and set newCustomer to customer.Clone(). then there's no need to attach customer since newCustomer is already attached.
in EF(4.1), I think You just have to attach the entity as modified:
myDbContext.Customers.AttachAsModified(customer, this.ChangeSet.GetOriginal(customer), myContext);
UPDATE:
Well, it seems like L2S needs original values of the entity. In reply to your comment, you have a couple choices: Using a timestamp column, returning a subset of entities, or having the original entity in your hand. In your scenario, you have the original entity already:
// This is your original entity
var newCustomer = db.Customers.Where(c => c.customer_id = customer.customer_id).SingleOrDefault();
So you will most probably can do:
if (customer != newCustomer)
{
myDbContext.Customers.Attach(customer, newCustomer);
}
Note: I'd rename newCustomer to originalCustomer if I were you since it's more related to the entity's state.
The problem with this approach is that you have an extra trip to database to get your original customer (newCustomer in your code). Take a look at here, here and definitely here to see how you can use TimeStamp columns to prevent the extra database trip.
I have finally gotten over one hurdle and can now successfully create new model data. Now there's another catch - validation. Most of the validation seems easy enough as a lot of my model data are scalar values. There is a many-to-many relationship I link to, however, so I'm not sure how to go about validating that. My model is (once again):
Game (only listing the relevant columns):
GameID - int (primary key, auto-incr)
Platform:
PlatformID - int (primary key, auto-incr)
Name - string
GamePlatform (not a visible entity):
GameID - int (foreign key from Games)
PlatformID - int (foreign key from Platforms)
And my Create method (yes, I know it's sloppy and amateurish - I am an amateur and trying to learn. I'll definitely add error checking to it. I'm just trying to get the big picture of the view->controller->validation->persist-in-db/show errors process down):
public ActionResult CreateReview([Bind(prefix = "GameData")]Game newGame, int[] PlatformIDs)
{
try
{
foreach(int i in PlatformIDs)
{
Platform plat = _siteDB.Platforms.Single(p => p.PlatformID == i);
newGame.Platforms.Add(plat);
}
newGame.LastModified = Datetime.Now;
_siteDB.Games.AddObject(newGame);
_siteDB.SaveChanges();
return RedirectToAction("Index");
}
catch
{
return View();
}
}
The array of PlatformIDs are supplied by a group of checkboxes within my view. For my Game to be valid, it must be associated with at least one Platform. I'm just not sure how to validate that with data annotations, or if it's even possible to do so. Any help would be greatly appreciated.
If I understand your question correctly, your int[] potentially contains ints associated with the ID of a Platform in your DB and you want to make sure your int[] contains at least one valid PlatformID, correct?
Immediately you could do just a simple check prior to going into your logic:
// If there aren't any IDs in Platform that are in PlatformIDs...
if (!_siteDB.Platforms.Any(p => PlatformIDs.Contains(p.PlatformID)))
Return RedirectToAction("Index");
// And probably tell the user to check a box, if they did,
// One of your checkboxes isn't matching up with your PlatformIDs
Ideally what you'd want to do is add the int[] to your model so you can check model validation. Since databased don't typically store int[], add it to your Game model. The EF probably put your DB Entities in your Models folder and if you look at them, you'll see they're partial classes. So add this in your Models folder:
public partial class Game
{
public Dictionary<int, bool> SupportedPlatforms { get; set; }// Edited
}
// Also add this which you'll see why below
public partial class Platform
{
public static bool IsValidPlatformID(int PlatformID)
{
using (SiteDBEntities _siteDB = new SiteDBEntities())
return _siteDB.Platforms.Any(p => p.PlatformID.Equals(PlatformID));
}
}
Then add a custom ValidationAttribute class:
public ContainsValidPlatformIDAttribute : ValidationAttribute
{
public ContainsValidPlatformIDAttribute() { }
public override bool IsValid(object value)
{
Dictionary<int, bool> supportedPlatforms = (Dictionary<int, bool>)value;
if (value == null)
return true;
foreach (int i in values)
{
if (supportedPlatforms.Values.Any(b => b.Equals(true)))// Edited
return false;
}
return true;
}
Now decorate your Property with it in the Game class:
[ContainsValidPlatformID(Error = "You did not select a valid Platform.")]
public Dictionary<int, bool> SupportedPlatforms { get; set; }// Edited
(Edited)Now instead of hard coding a checkbox for each platform, add this instead:
<%: Html.CheckboxFor(model => model.SupportedPlatforms[0]) %>
<%: Html.ValidationMessageFor(model => model.SupportedPlatforms[0]) %>
(Edited)Now your checkboxes are tied to the Model, you can validate the model in the controller, and you can remove the int[] argument from your Action method. This has all been coded from my head into this editor so you may need to tweak some things here and there but this is the direction you should be heading in when working with Models in Views.
Also, check out what Scott Guthrie has written on the topic of MVC Model Validation in his blog. Hopefully with my sample and Scott's blog, you'll be pointed in the right direction.
i'm trying to wrap my head around how to enterprise up my code: taking a simple routine and splitting it up into 5 or 6 methods in 3 or 4 classes.
i quickly came up three simple examples of code how i currently write it. Could someone please convert these into an MVC/MVP obfuscated version?
Example 1: The last name is mandatory. Color the text box red if nothing is entered. Color it green if stuff is entered:
private void txtLastname_TextChanged(object sender, EventArgs e)
{
//Lastname mandatory.
//Color pinkish if nothing entered. Greenish if entered.
if (txtLastname.Text.Trim() == "")
{
//Lastname is required, color pinkish
txtLastname.BackColor = ControlBad;
}
else
{
//Lastname entered, remove the coloring
txtLastname.BackColor = ControlGood;
}
}
Example 2: The first name is optional, but try to get it. We'll add a bluish tint to this "try to get" field:
private void txtFirstname_TextChanged(object sender, EventArgs e)
{
//Firstname can be blank.
//Hint them that they should *try* to get it with a bluish color.
//If they do enter stuff: it better be not all spaces.
if (txtFirstname.Text == "")
{
//Nothing there, hint it blue
txtFirstname.BackColor = ControlRequired;
}
else if (txtFirstname.Text.Trim() == "")
{
//They entered spaces - bad user!
txtFirstname.BackColor = ControlBad;
}
else
{
//Entered stuff, remove coloring
txtFirstname.BackColor = SystemColors.Window;
}
}
Example 3 The age is totally optional. If an age is entered, it better be valid:
private void txtAge_TextChanged(object sender, EventArgs e)
{
//Age is optional, but if entered it better be valid
int nAge = 0;
if (Int32.TryParse(txtAge.Text, out nAge))
{
//Valid integer entered
if (nAge < 0)
{
//Negative age? i don't think so
txtAge.BackColor = ControlBad;
}
else
{
//Valid age entered, remove coloring
txtAge.BackColor = SystemColors.Window;
}
}
else
{
//Whatever is in there: it's *not* a valid integer,
if (txtAge.Text == "")
{
//Blank is okay
txtAge.BackColor = SystemColors.Window;
}
else
{
//Not a valid age, bad user
txtAge.BackColor = ControlBad;
}
}
}
Every time i see MVC code, it looks almost like random splitting of code into different methods, classes, and files. i've not been able to determine a reason or pattern to their madness. Without any understanding of they why it's being one some way, it makes no sense. And using the words model, view, controller and presenter, like i'm supposed to know what that means, doesn't help.
The model is your data.
The view shows data on screen.
The controller is used to carry out
the users actions
And oranges taste orangy.
Here's my attempt at splitting things up in order to make the code more difficult to follow. Is this anywhere close to MVC?
private void txtFirstname_TextChanged(object sender, EventArgs e)
{
FirstnameTextChangedHandler(sender, e);
}
private void FirstnameTextChangedHandler(sender, e)
{
string firstname = GetFirstname();
Color firstnameTextBoxColor = GetFirstnameTextBoxColor(firstname);
SetFirstNameTextBoxColor(firstnameTextBoxColor);
}
private string GetFirstname()
{
return txtFirstname.Text;
}
private Color GetFirstnameTextBoxColor(string firstname)
{
//Firstname can be blank.
//Hint them that they should *try* to get it with a bluish color.
//If they do enter stuff: it better be not all spaces.
if (firstname == "")
{
//Nothing there, hint it blue
return GetControlRequiredColor();
}
else if (firstname.Trim() == "")
{
//They entered spaces - bad user!
return GetControlBadColor();
}
else
{
//Entered stuff, remove coloring
return GetControlDefaultColor();
}
}
private Color GetControlRequiredColor()
{
return ControlRequired;
}
private Color GetControlBadColor()
{
return ControlBad;
}
private Color GetControlGoodColor()
{
return ControlGood;
}
//am i doin it rite
i've obfuscated the code, but it's still altogether. The next step in the MVC obfuscation, i gather, is to hide the code in 3 or 4 different files.
It's that next step that i don't understand. What is the logical separation of which functions are moved into what other classes? Can someone translate my 3 simple examples above into full fledged MVC obfuscation?
Edit: Not ASP/ASP.NET/Online. Pretend it's on a desktop, handheld, surface, kiosk. And pretend it's language agnostic.
The purpose of MVC/MVP patterns is not obfuscation, but separation of concerns. Obfuscation is to (conceal the) intended meaning in communication, making communication confusing, intentionally ambiguous, and more difficult to interpret: ref. The use of patterns is to make the code cleaner and more understandable. I suggest you start out by reading the wikipedia entries on MVC and MVP.
Both patterns are ways of structuring your code so that your application is broken up into elements that carry out specific purposes that have clearly defined interaction boundaries. Rather than having code that specifically addresses business concerns, input/output handling, and presentation throughout the various classes of the application, these concerns are separated and isolated in the various architectural components. These architectural elements are insulated from one another by the interaction boundaries (interfaces) making them more independent of one another and easier to modify without affect the application as a whole.
The main idea I have when implementing MVC for Windows Forms is that I want to have unit tests for my model and my controller. In order to achieve that, my controller should not know anything about the views using it, and so any notifications that should be handled on UI level are implemented as events. In your example, my controller would look something like this:
class Controller
{
// This is the model we are operating on
private Model model_;
public enum Status
{
Normal,
Required,
Good,
Bad
}
public delegate void FirstNameStatusChangedDelegate(Status newStatus);
public event FirstNameStatusChangedDelegate FirstNameStatusChangedEvent;
public string FirstName
{
get { return model_.FirstName; }
set
{
if (value == "")
RaiseFirstNameStatusChanged(Status.Required);
else if ( value.Trim() == "" )
RaiseFirstNameStatusChanged(Status.Bad);
else
{
model_.FirstName = value;
RaiseFirstNameStatusChanged(Status.Normal);
}
}
}
private void RaiseFirstNameStatusChanged(Status newStatus)
{
if ( FirstNameStatusChangedEvent != null )
FirstNameStatusChangedEvent(newStatus);
}
}
And the view would provide handlers for the FirstNameStatusChanged event:
class View : Form
{
private Controller controller_;
private static readonly Dictionary<Controller.Status, Color> statusColors_ = new Dictionary<Controller.Status, Color>
{
{Controller.Status.Normal, SystemColors.Window},
{Controller.Status.Required, ControlRequired},
{Controller.Status.Good, ControlGood},
{Controller.Status.Bad, ControlRed}
};
public View(Controller controller)
{
InitializeComponent();
controller_ = controller;
contoller_.FirstNameStatusChangedEvent += OnFirstNameStatusChanged;
}
private void txtFirstname_TextChanged(object sender, EventArgs e)
{ controller_.FirstName = txtFirstName.Text; }
private void OnFirstNameStatusChanged(Controller.Status newStatus)
{ txtFirstName.BackColor = statusColors_[newStatus]; }
}
Most of what you doing in your code belongs to the Controller class since it describes the the logic. Your View should just describe UI and give easy access to UI components. Model class should describe your data model.
The idea is simple: Controller does everything, but it has to know about the View and the Model. For example as View is initialized, Controller sets up all the logic ( kinda what you already doing). As Model is assigned to the Controller - it sets the values into appropriate UI controls and does the same to retrieve data and return is as Model.
So basically you give your data model class to the controller, it does the editing and returns your data as model class again.
It would be very hard to follow MVC in classic ASP.NET if possible, so I will reply based on MVP.
On your first example, you are trying to do a validation. Validating a surname is the responsibility of Presenter. Showing the field red is the responsibility of View. So, your view class would be like this:
private void Page_Load()
{
this._presenter = new Presenter();
}
private void txtLastname_TextChanged(object sender, EventArgs e)
{
txtLastName.BackColor = presenter.IsLastnameValid(txtLastName.Text) ?
ControlGood : ControlBad;
}
And your presenter class would be something like this:
public Presenter()
{
public bool IsLastNameValid(string lastname)
{
return string.IsNullOrEmpty(lastname);
}
}
Last name is your model here.
Please note that I prepared this classes only for showing how would you form an MVP structure. In real world, there are lots of better ways to do validation. Normally you would use this approach for your business instead of validation.
Ian,
If you want the controls to validate immediately, you need to use javascript or jQuery. This is also true for classic ASP.NET. Since you are using Code Behind methods, I assume that your validation waits for a postback.
The following examples are from the NerdDinner project. NerdDinner is an open source project that serves as an example of ASP.NET MVC architecture. The authors have graciously provided a tutorial with it, available at http://nerddinnerbook.s3.amazonaws.com/Intro.htm
When a form is submitted in ASP.NET MVC, it enters the corresponding controller as a FormCollection object:
[AcceptVerbs(HttpVerbs.Post)]
public ActionResult Edit(int id, FormCollection formValues) {
Dinner dinner = dinnerRepository.GetDinner(id);
try
{
UpdateModel(dinner);
dinnerRepository.Save();
}
catch
{
ModelState.AddModelErrors(dinner.GetRuleViolations())
}
return RedirectToAction("Details", new { id = dinner.DinnerID });
}
UpdateModel takes the form values and attempts to stuff them into the dinner object. The dinner object looks like this:
public partial class Dinner {
public bool IsValid {
get { return (GetRuleViolations().Count() == 0); }
}
public IEnumerable<RuleViolation> GetRuleViolations() {
yield break;
}
public IEnumerable<RuleViolation> GetRuleViolations() {
if (String.IsNullOrEmpty(Title))
yield return new RuleViolation("Title is required", "Title");
if (String.IsNullOrEmpty(Description))
yield return new RuleViolation("Description is required", "Description");
if (String.IsNullOrEmpty(HostedBy))
yield return new RuleViolation("HostedBy is required", "HostedBy");
if (String.IsNullOrEmpty(Address))
yield return new RuleViolation("Address is required", "Address");
if (String.IsNullOrEmpty(Country))
yield return new RuleViolation("Country is required", "Address");
if (String.IsNullOrEmpty(ContactPhone))
yield return new RuleViolation("Phone# is required", "ContactPhone");
if (!PhoneValidator.IsValidNumber(ContactPhone, Country))
yield return new RuleViolation("Phone# does not match country", "ContactPhone");
yield break;
}
partial void OnValidate(ChangeAction action) {
if (!IsValid)
throw new ApplicationException("Rule violations prevent saving");
}
}
Notice the IsValid method and the RuleViolations enumerator. If everything is set up properly, all you have to do is define your validations in here, and ASP.NET MVC will take care of the rest for you.
The final validated result looks like this:
I encourage you to get the NerdDinner application and tutorial at http://nerddinner.codeplex.com/