141

I have a class called 'Article' in a project called 'MyProject.Data', which acts as the data layer for my web application.

I have a separate project called 'MyProject.Admin', which is a web-based admin system for viewing/editing the data, and was build using ASP.NET Dynamic Data.

Basically I want to extend the Article class, using a partial class, so that I can augment one of its properties with a "UIHint" extender, which will allow me to replace the normal multi-line textbox with an FCKEdit control.

My partial class and extender would look like this:

[MetadataType(typeof(ProjectMetaData))]
public partial class Project
{
}

public class ProjectMetaData
{
    [UIHint("FCKeditor")]
    public object ItemDetails { get; set; }
}

Now this all works fine if the partial class is in the same project as the original partial class - i.e. the MyProject.Data project.

But UI behavior shouldn't sit in the Data layer, but rather, in the Admin layer. So I want to move this class to MyProject.Admin.

However, if I do that, the functionality is lost.

My fundamental question is: can I have 2 partial classes in separate projects, but both referring to the same "class"?

If not, is there a way to accomplish what I'm trying to do, without mixing data-layer logic with UI logic?

Marius Schulz
  • 15,976
  • 12
  • 63
  • 97
Jonathan
  • 32,202
  • 38
  • 137
  • 208
  • 2
    This is precisely why the concept of MetadataType stinks. (http://en.wikipedia.org/wiki/Code_smell). It is a completely flawed solution - You are trying to build MVC which specifically separates model from view from controller and you need view and validation logic in the data classes. Rediculous. There should be a better way of applying these attributes. You should be able to associate a metadata class with a data class using a fluent API or something similar. It should not be baked in. – Jim Nov 08 '11 at 08:10
  • Some other answers mention this: If it's an absolute must, and you own the referenced assembly source, you could always include the source models as linked files (split-button on the Add-Existing-Item file picker) so they are built with the consuming instead of assembly ref. (Similar strategy to exposing your Model/Data layer via WCF with a Service Reference and extending those partial code-gen'ed classes.) You are never forced to smash layers - you can always subclass. And `MetadataType` makes Models more like ViewModels. – JoeBrockhaus Dec 17 '15 at 19:26
  • Its too late to respond, but i have provide a solution [Here](http://stackoverflow.com/questions/17186379/putting-dataannotation-buddy-class-in-another-assembly/37343388#37343388) – Usman May 20 '16 at 10:08
  • I know its too late to respond, but [Here](http://stackoverflow.com/questions/17186379/putting-dataannotation-buddy-class-in-another-assembly/37343388#37343388) i have presented a solution. – Usman May 20 '16 at 10:10

9 Answers9

200

No, you cannot have two partial classes referring to the same class in two different assemblies (projects). Once the assembly is compiled, the meta-data is baked in, and your classes are no longer partial. Partial classes allows you to split the definition of the same class into two files.

Kees C. Bakker
  • 32,294
  • 27
  • 115
  • 203
Darin Dimitrov
  • 1,023,142
  • 271
  • 3,287
  • 2,928
16

As noted, partial classes is a compile-time phenomenon, not runtime. Classes in assemblies are by definition complete.

In MVC terms, you want to keep view code separate from model code, yet enable certain kinds of UI based on model properties. Check out Martin Fowler's excellent overview of the different flavours of MVC, MVP and whatnot: you'll find design ideas aplenty. I suppose you could also use Dependency Injection to tell the UI what kind of controls are viable for individual entities and attributes.

Your aim of separating concerns is great; but partial classes were intended to address entirely different issues (primarily with code generation and design-time modelling languages).

Pang
  • 9,564
  • 146
  • 81
  • 122
Pontus Gagge
  • 17,166
  • 1
  • 38
  • 51
9

Extension methods and ViewModels are the standard way to extend data-layer objects in the frontend like this:

Data Layer (class library, Person.cs):

namespace MyProject.Data.BusinessObjects
{
  public class Person
  {
    public string Name {get; set;}
    public string Surname {get; set;}
    public string Details {get; set;}
  }
}

Display Layer (web application) PersonExtensions.cs:

using Data.BusinessObjects
namespace MyProject.Admin.Extensions
{
  public static class PersonExtensions
  {
    public static HtmlString GetFormattedName(this Person person)
    {
       return new HtmlString(person.Name + " <b>" + person.Surname</b>);
    }
  }
}

ViewModel (for extended view-specific data):

using Data.BusinessObjects
namespace MyProject.Admin.ViewModels
{
  public static class PersonViewModel
  {
    public Person Data {get; set;}
    public Dictionary<string,string> MetaData {get; set;}

    [UIHint("FCKeditor")]
    public object PersonDetails { get { return Data.Details; } set {Data.Details = value;} }
  }
}

Controller PersonController.cs:

public ActionMethod Person(int id)
{
  var model = new PersonViewModel();
  model.Data = MyDataProvider.GetPersonById(id);
  model.MetaData = MyDataProvider.GetPersonMetaData(id);

  return View(model);
}

View, Person.cshtml:

@using MyProject.Admin.Extensions

<h1>@Model.Data.GetFormattedName()</h1>
<img src="~/Images/People/image_@(Model.MetaData["image"]).png" >
<ul>
  <li>@Model.MetaData["comments"]</li>
  <li>@Model.MetaData["employer_comments"]</li>
</ul>
@Html.EditorFor(m => m.PersonDetails)
8DX
  • 261
  • 2
  • 4
  • The Extensions comment makes quite a bit of sense, this can be completely decoupled from the Person object by using an interface. I like it! – Pale Ale Nov 15 '18 at 23:12
2

Add the base file as a linked file into your projects. It's still partial but it allows you to share it between both projects, keep them synchronized and at the same time have version/framework specific code in the partial classes.

Pang
  • 9,564
  • 146
  • 81
  • 122
leon.io
  • 2,779
  • 1
  • 18
  • 26
2

Perhaps use a static extension class.

Braneloc
  • 21
  • 1
  • 1
    Good idea. Can you provide an example of what you think would provide sufficient functionality within your answer? – pvanhouten Oct 26 '12 at 16:20
1

I've had similar issues with this. I kept my partial classes in my Data project so in your case the 'MyProject.Data'. MetaDataClasses shouldn't go in your Admin project as you will create a circular references other wise.

I added a new Class Lib project for my MetaDataClasses e.g. 'MyProject.MetaData' and then referenced this from my Data project

Indy
  • 283
  • 1
  • 2
  • 9
1

Just add class file as link in your new project and keep the same namespace in your partial class.

user
  • 86,916
  • 18
  • 197
  • 190
nl20121974
  • 19
  • 1
0

I may be mistaken here, but could you not simply define the ProjectMetaData class in your MyProject.Admin project?

Darragh
  • 2,526
  • 1
  • 23
  • 31
0

Since 2019 you can have 2 parts of a partial class in different assemblies using a trick. This trick is explained and demonstrated in this article:

https://www.notion.so/vapolia/Secret-feature-Xamarin-Forms-control-s-auto-registration-1fd6f1b0d98d4aabb2defa0eb14961fa

It uses at its core the MSBuild.Sdk.Extras extension to SDK like projects, which solves the limitation of having all partial parts of a class in the same assembly, by using one project with multiple simultaneous targets, effectively creating multiples assemblies in one compilation of the same project.

Softlion
  • 12,281
  • 11
  • 58
  • 88