I am trying to understand the factory pattern where the factory takes a MeetingPollingQuestionType and returns the UI/MVC appropriate for the question type. I created this interface MeetingQuestionInterface
and one class LongAnswerText
that has a label
and TextBox
. When I run the program how do I send the view to the factory?
I am not sure how to pass in the
MeetingPollingQuestionType
and get this data populated? Should the interface take inMeetingPollingQuestionType
? Any help would be great.
Goal
the factory takes a MeetingPollingQuestionType and returns the UI/MVC appropriate for the question type.
JSON Data
[
{
"MeetingPollingQuestionId": 2,
"MeetingPollingQuestionType": "LongAnswerText",
"MeetingPollingId": 3,
"SequenceOrder": 1,
"MeetingPollingParts": [
{
"MeetingPollingPartsId": 2,
"Type": "Question",
"MeetingPollingQuestionId": 2,
"MeetingPollingPartsValues": [
{
"Type": "label",
"QuestionValue": "This is a long question",
"FileManagerId": 0,
"FileName": null,
"FileData": null,
"FileType": null
}
]
}
]
},
{
"MeetingPollingQuestionId": 3,
"MeetingPollingQuestionType": "MultipleChoice",
"MeetingPollingId": 3,
"SequenceOrder": 2,
"MeetingPollingParts": [
{
"MeetingPollingPartsId": 3,
"Type": "Question",
"MeetingPollingQuestionId": 3,
"MeetingPollingPartsValues": [
{
"Type": "label",
"QuestionValue": "this is a multiple choice question",
"FileManagerId": 0,
"FileName": null,
"FileData": null,
"FileType": null
}
]
},
{
"MeetingPollingPartsId": 4,
"Type": "Image",
"MeetingPollingQuestionId": 3,
"MeetingPollingPartsValues": [
{
"Type": "Image",
"QuestionValue": null,
"FileManagerId": 14552,
"FileName": null,
"FileData": null,
"FileType": null
}
]
},
{
"MeetingPollingPartsId": 5,
"Type": "Answers",
"MeetingPollingQuestionId": 3,
"MeetingPollingPartsValues": [
{
"Type": "radio",
"QuestionValue": "Yes",
"FileManagerId": 0,
"FileName": null,
"FileData": null,
"FileType": null
},
{
"Type": "radio",
"QuestionValue": "No",
"FileManagerId": 0,
"FileName": null,
"FileData": null,
"FileType": null
},
{
"Type": "radio",
"QuestionValue": "Abstain",
"FileManagerId": 0,
"FileName": null,
"FileData": null,
"FileType": null
}
]
}
]
}
]
Program
static void Main(string[] args)
{
LongAnswerText LongAnswerTextParts = new LongAnswerText();
var control = LongAnswerTextParts ()
}
interface MeetingQuestionInterface
{
string Label(string target, string text);
}
public class LongAnswerText : MeetingQuestionInterface
{
public static string Label(string target, string text)
{
return String.Format("<label for='{0}'>{1}</label>", target, text);
}
public static string TextBox(string target, string text)
{
return String.Format("<input for='{0}'>{1}</input>", target, text);
}
}
MVC form view
<div >
@Html.LabelFor(c => c.LongAnswerText)
@Html.TextBoxFor(c => c.LongAnswerText, new { @class = "form-control" })
</div>
Sample
https://dotnetfiddle.net/j6YIPN
CodePudding user response:
So I built a small example component for this use case, which I think works here, but depending on the data model might be optimized a bit more.
This is the actual component doing the strategising, so I called it StrategyComponent:
@switch (Input.Type)
{
case "label":
<label for="@id">@Input.QuestionValue</label>
break;
case "text":
<input id="@id" type="text"/>
break;
}
@code {
[Parameter]
public MeetingPollingPartsValues Input { get; set; } = null!;
private readonly Guid id = Guid.NewGuid(); // Because I'm not sure where to take the ID from
}
Then to use it you just do this:
@foreach (var value in Values)
{
<StrategyComponent Input="value" />
}
@code {
List<MeetingPollingPartsValues> Values { get; set; } = new(); // Put real data here
}
I hope I got the data model right, I just copied it from your fiddle. For the things around that it seems there is another model that also has its own type, I guess you can use the same pattern there.
CodePudding user response:
There is a NUGet Package out there that does this and more, but I prefer some manual control and don't mind implementing the pattern myself. I use Json.NET along with many others, so this solution depends on that. This is just a mockup, untested, but the model is extensible and polymorphic.
have your classes inherit from an abstract base class with the type property
Have your types inherit from this base and override the type.
Define a ModelType Enumeration class. This can be overkill, but I prefer classes over enumerations - a pattern borrowed from java.
Define a string converter for the type to allow implicit cast from the ModelType.
Define a Model Converter for Json Deserialization based on type.
To deserialize, use converter settings:
void Main()
{
var json = "..."; var ConverterSettings = new JsonSerializerSettings { Formatting = Newtonsoft.Json.Formatting.None, Converters = { new ImplicitStringConverter(), new ModelConverter() } }; JsonConvert.DeserializeObject<ModelBase>(json, ConverterSettings);
}
public abstract class ModelBase { [JsonConverter (typeof (ImplicitStringConverter))] public virtual ModelType ModelType { get; } }
public class QuestionModel : ModelBase {
public override ModelType ModelType => ModelType.Question;
}
public class ModelConverter : JsonConverter { public override bool CanConvert (Type objectType) { return objectType.Equals (typeof (ModelBase)); }
public override object ReadJson (JsonReader reader, Type objectType, object existingValue, JsonSerializer serializer) { /// /// Load the reader as a JObject to extract the datatype /// JObject obj = JObject.Load (reader); ModelType actionType = obj["ModelType"].ToString(); object resultInstance = null; /// /// Use NewtonSoft deserialization on the typed instance /// if (actionType == ModelType.Question) { resultInstance = new QuestionModel(); serializer.Populate (obj.CreateReader(), resultInstance); } return resultInstance; } public override void WriteJson (JsonWriter writer, object value, JsonSerializer serializer) { JToken t = JToken.FromObject (value); t.WriteTo (writer); }
}
[JsonConverter (typeof (ImplicitStringConverter))] public class ModelType {
public static readonly ModelType Question = new ModelType ("Question", "Question", isDefault: true); /// @future Add additional static read only types supported by this connector protected ModelType (string name, string description = null, bool isDefault = false) { Name = name; IsDefault = isDefault; Description = description; } public static ModelType Default { get { return All.FirstOrDefault (a => a.IsDefault == true); } } public static IEnumerable<ModelType> All { get { return new List<ModelType>() { Question }; /** @future Add additional static read only types supported by this connector **/} } public string Name { get; private set; } public bool IsDefault { get; private set; } public string Description { get; private set; } public static explicit operator string (ModelType value) => value.Name; public static implicit operator ModelType (string value) => ModelType.FromName (value); public static ModelType FromName (string name) => All.FirstOrDefault (a => string.Equals (a.Name, name, StringComparison.InvariantCultureIgnoreCase)); public override string ToString() => this.Name;
}
class ImplicitStringConverter : JsonConverter {
public override void WriteJson (JsonWriter writer, object value, JsonSerializer serializer) { JToken t = JToken.FromObject (value.ToString()); t.WriteTo (writer); } public override object ReadJson (JsonReader reader, Type objectType, object existingValue, JsonSerializer serializer) { //throw new NotImplementedException("Unnecessary because CanRead is false. The type will skip the converter."); var value = reader.Value?.ToString() ?? null; var method = objectType.GetMethod ("op_Implicit"); var obj = method?.Invoke (null, new object[] { value }); return obj; } public override bool CanRead { get { return true; } } public override bool CanConvert (Type objectType) { return objectType.GetMembers().Any (t => t.Name == "op_Implicit" && t.ReflectedType == typeof (string)); }
}