I got redirected from here: https://github.com/aspnet/AspNetCore/issues/11963
I'm in the process of converting a solution over from .Net 4.6 and i'm looking at metadata.
In the old solution I had a custom implementation of the data annotations metadata provider which I had extended like this ....
public class ApiMetadataProvider : DataAnnotationsModelMetadataProvider, IDisposable
{
public IResourceProvider ResourceProvider { get; }
public ICoreDataContext CoreDb { get; }
public ApiMetadataProvider(IResourceProvider resourceProvider, ICoreDataContext core)
{
ResourceProvider = resourceProvider;
CoreDb = core;
}
protected override ModelMetadata CreateMetadata(
IEnumerable<Attribute> attributes,
Type containerType,
Func<object> modelAccessor,
Type modelType,
string propertyName)
{
ModelMetadata modelMetadata = base.CreateMetadata(
attributes,
containerType,
modelAccessor,
modelType,
propertyName);
Type serverType = (modelType == typeof(string))
? typeof(string)
: modelType.ImplementsGenericInterface(typeof(IEnumerable<>)) ?? modelType;
if (serverType.IsGenericType && serverType.Name.StartsWith("Nullable") && typeof(Nullable<>).MakeGenericType(serverType.GenericTypeArguments) == serverType) { serverType = serverType.GenericTypeArguments[0]; }
modelMetadata.AdditionalValues.Add("ServerType", serverType.AssemblyQualifiedName);
SetTemplateHint(modelMetadata);
SetCustomAttributes(attributes, modelMetadata, modelType, propertyName);
SetResourceStrings(modelMetadata);
return modelMetadata;
}
....
}
... the key thing here is that I pull the base copy of the model meta for the given type and then manipulate it in my own custom ways (some of which is shown in the sample above).
I cut the rest out because there's quite a bit of it.
The net result is that from my own base generic controller I had an action that looked like this ...
protected MetadataContainer GetMetadataForType(Type type)
{
return new MetadataContainer(MetaProvider.GetMetadataForType(null, type));
}
Controllers would then often make decisions based on this.
I am looking to reproduce this behaviour, the key thing being the ability to get a customised version of the "final meta" from the stack (I gather from this: https://github.com/aspnet/Mvc/issues/2522 ... that meta is now a "chain of providers" in some fashion).
So I have a couple of questions ....
How can i add or remove / update custom "properties" / attributes in the meta information for a given type?
How can I get an instance of the meta that the stack sees as being the "final result" after all providers have been executed?
The existing solution often handed this meta information to client JS code to allow for "dynamic component construction" in the browser, is this a scenario that has any form of best practice that I can gather more advice from (perhaps you guys have a blog post or something to get me started)?
The answer was simple in the end ...
Don't bother doing any of this, build a cache of the reflection derived information and in the controller just serve it up.
This basically just means this is simple reflection code to extract the relevant type info wanted.
Related
I'm using the HL7.Fhir nuget package 0.9.3 created by Ewout Kramer.
I am tying it up with ASP.NET Web API, but unfortunately the built in JSON serialization isn't generating the JSON correctly. It contains lots of this:
"<IdentifierElement>k__BackingField"
As suggested by the framework, this code works...
public HttpResponseMessage GetConformance()
{
var conformance = new Conformance();
var json = FhirSerializer.SerializeResourceToJson(conformance);
return new HttpResponseMessage{Content = new StringContent(json)};
}
but this will become quite repetitive and isn't following the "by convention" json/xml serialization methods of Web API.
Are there any other FHIR objects packages available or should I just write my own?
Although the newer version of the HL7.Fhir NuGet package (currently in beta) will carry additional [DataContract] and [DataMember] attributes, and thus prevent these kind of errors, the standard .NET DataContract serializer will not be able to serialize in-memory POCO's to the correct FHIR XML and Json representation. The FHIR serialization has specific rules about how both XML and json are used, which is hard, if not impossible, to configure using the (limited) possibilities of the DataContract serializer.
However, it's also not necessary to invoke the FhirSerializer for each call as you showed in your codesnippet (in fact, that would be an WebApi anti-pattern). For example, our FHIR server (at http://spark.furore.com/fhir) is based on WebApi and uses a custom MediaTypeFormatter to handle this. To get a taste of what that looks like, we have created two formatters, one for json and one for xml:
public class JsonFhirFormatter : MediaTypeFormatter
{
public JsonFhirFormatter() : base()
{
foreach (var mediaType in ContentType.JSON_CONTENT_HEADERS)
SupportedMediaTypes.Add(new MediaTypeHeaderValue(mediaType));
}
}
This tells the framework this formatter will take any of the formats in ContentType.JSON_CONTENT_HEADERS (which are application/json and some common variants) and is able to parse and read FHIR ModelTypes:
public override bool CanReadType(Type type)
{
return type == typeof(ResourceEntry) || type == typeof(Bundle) || (type == typeof(TagList));
}
public override bool CanWriteType(Type type)
{
return type == typeof(ResourceEntry) || type == typeof(Bundle) || (type == typeof(TagList)) || type == typeof(OperationOutcome);
}
Finally, you have to override the ReadFromStreamAsync and WriteToStreamAsync methods:
public override Task WriteToStreamAsync(Type type, object value, Stream writeStream, HttpContent content, TransportContext transportContext)
{
// Some code left out...
XmlWriter writer = new XmlTextWriter(writeStream, Encoding.UTF8);
if (type == typeof(ResourceEntry))
{
ResourceEntry entry = (ResourceEntry)value;
FhirSerializer.SerializeResource(entry.Resource, writer);
content.Headers.SetFhirTags(entry.Tags);
}
Now, once you've done that, your Controller can simply do:
[HttpGet, Route("metadata")]
public ResourceEntry Metadata()
{
return service.Conformance();
}
[HttpOptions, Route("")]
public ResourceEntry Options()
{
return service.Conformance();
}
Note that our server does not use Resources as parameters and return values in the controller. Resources won't allow you to capture important metadata (like the id, version-id, last modified date etc). By using ResourceEntry in my controller, this data can be passed around with the resource data and the WebApi framework can bind this metadata to the appropriate HTTP headers.
I am using Automapper within an ASP.Net MVC application to map DTO's to ViewModel objects.
in one of my mappings I need access to an object stored in the Session object.
public override void OnAuthorization(AuthorizationContext filterContext)
{
...
SecurityToken token = SecurityTokenFactory.CreateSecurityToken(userNode);
filterContext.HttpContext.Session[securityToken] = token;
...
}
In the constructor of my controller I set up the Automapper mapping.
Mapper.CreateMap<UserReportDTO, UserDefinedReportModel>()
.ForMember(dest => dest.IsEditable, opt=>opt.ResolveUsing(src => this.IsEditable(src)));
private bool IsEditable(UserReportDTO report)
{
if (this.GetCurrentUserToken().UserVisibilityLevel == VisibilityLevel.Root)
{
return true;
}
return false;
}
public JsonResult GetVisibleUserReports()
{
...
int ID = this.GetCurrentUserToken().UserId; //This works!
var reports = Mapper.Map < UserReportDTO[], UserDefinedReportModel[] >(inputarray); //This doesn't work
...
}
What happens is that the context.Session is null.
I'm guessing this is something to do with the way Automapper resolves the mapping - maybe a reference to one Context is set when the mapping is created, and then this Context no longer exists at mapping time?
How can I resolve the issue - is there a way to pass a parameter to a mapping operation?
My temporary workaround is to map all the other fields, and then manually loop through the mapped-collection, setting the field that requires the current context, but I'm loathe to keep this approach.
A couple thoughts that might put you on the right track:
Does it make any difference if you replace ResolveUsing with MapFrom? Both seem to accept a Func<TSource, TMember>, but perhaps there are subtle differences.
Would it be possible to turn your IsEditable method into an IValueResolver then pass the required session data into the constructor using AutoMapper's ConstructedBy() feature? Here's the relevant documentation. Scroll to the "Custom constructor methods" section.
I am using the example at The Complete Guide To Validation In ASP.NET MVC 3 to create a RequiredIf validation attribute (it's about 1/3 down the page under the heading of "A more complex custom validator"). It all works fine with the exception of one scenario, and that is if I have the need to validate against a complex type. For example, I have the following model:
public class MemberDetailModel
{
public int MemberId { get; set; }
// Other model properties here
public MemberAddressModel HomeAddress { get; set; }
public MemberAddressModel WorkAddress { get; set; }
}
public class MemberAddressModel
{
public bool DontUse { get; set; }
// Other model properties here
[RequiredIf("DontUse", Comparison.IsEqualTo, false)]
public string StreetAddress1 { get; set; }
}
The problem is that when the attribute validation for the StreetAddress property is rendered, it get's decorated with the attribute of data-val-requiredif-other="DontUse". Unfortunately, since the address is a sub-type of the main model, it needs to be decorated with a name of HomeAddress_DontUse and not just DontUse.
Strangely enough, the validation works fine for server-side validation, but client-side unobtrusive validation fails with an JS error because JS can't find the object with a name of just "DontUse".
Therefore, I need to find a way to change the ModelClientValidationRequiredIfRule method to know that the property it is validating is a sub-type of a parent type, and if so, prepend the ParentType_ to the "otherProperty" field (e.g. otherProperty becomes HomeAddress_DontUse.
I have tried passing in typeof(MemberAddressModel) as a parameter of the attribute, but even when debugging the attribute creation, I can't seem to find any reference to the parent type of HomeAddress or WorkAddress from that type.
Based on the suggestion from The Flower Guy, I was able to come up with the following which seems to work. I simply modified the following in the customValidation.js file:
jQuery.validator.addMethod("requiredif", function (value, element, params) {
if ($(element).val() != '') return true;
var prefix = getModelPrefix(element.name); // NEW LINE
var $other = $('#' + prefix + params.other); // MODIFIED LINE
var otherVal = ($other.attr('type').toUpperCase() == "CHECKBOX") ? ($other.attr("checked") ? "true" : "false") : $other.val();
return params.comp == 'isequalto' ? (otherVal != params.value) : (otherVal == params.value);
});
I also added the following method to that file (within the JQuery block so as to be only privately accessible):
function getModelPrefix(fieldName) {
return fieldName.substr(0, fieldName.lastIndexOf(".") + 1).replace(".","_");
}
Cannot do it exactly right now, but the problem is in the client javascript function:
jQuery.validator.addMethod("requiredif" ...
The js is not sophisticated enough to cope with complex view models where there may be a model prefix. If you take a look at Microsoft's jquery.validate.unobstrusive.js (in the Scripts folder over every MVC3 application), you will find some useful methods including getModelPrefix and appendModelPrefix. You can take a similar approach and change the requiredIf validation method - take a look at the equalto method in jquery.validate.unobstrusive.js for a helping hand.
I have a model with some inheritance worked into it, but here's the thing, I want to apply different HTML attributes depending on which type it is. So my thought was to create a property, I'll call it DisplayString, which is used as the first argument to String.Format. DisplayString is used to allow me to do something like this:
Classes:
public class Reaction
{
public string ReactionString { get; set; }
public string DisplayString { get; set; }
public Reaction(string reactionString)
{
ReactionString = reactionString;
DisplayString = "It was {0}!"
}
public Reaction(string reactionString, string displayString)
{
ReactionString = reactionString;
DisplayString = displayString;
}
}
public class GoodReaction : Reaction
{
public GoodReaction()
{
base("awesome");
}
}
public class BadReaction : Reaction
{
public GoodReaction()
{
base("horrible");
}
}
public class AverageReaction : Reaction
{
public GoodReaction()
{
base("alright", "It kinda was {0}...");
}
}
View:
#model Reaction
#String.Format(Model.DisplayString, "<strong>" + Model.ReactionString + "</strong>");
So basically every subclass has a string I want to display, but I have varying text AND varying markup. Of course doing it this way just results with the strong tags rendered as text. So what are my options? Feel free to ask any questions needed to clarify, hopefully I got my question across properly. Thanks!
EDIT:
So I wrapped it in Html.Raw() and that did the trick, although I'm sure there is something bad about doing that and I should probably encode each piece, then add tags, then raw, or something, but for my purposes it works.
I would create a display template for each concrete class, and MVC will be smart enough to pick the right one.
E.g when you call #Html.DisplayFor(model => model.Reaction), where Reaction is declared as the base class, MVC will work out what type it is, then look for a template matching that type, if it doesn't find it, it will look for a template of the base type, then finally fallback to the default template. I do this a lot in my application.
Sounds like you should also make the Reaction class abstract, and the ctor's protected.
In our code we have:
public interface ILogMagazine
{
string Text { get; set; }
DateTime DateAndTime { get; set; }
string DetailMessage { get; set; }
}
SimpleDataContext: DataContext
{
public Table<ILogMagazine> LogMagaines
{
get { return GetTable<ILogMagazine>(); }
}
}
We try to:
DataContext db = new SimpleDataContext("...");
ILogMagazine lg = new LogMagazine()
{
Text = "test",
DateAndTime = DateTime.Now,
DetailMessage = "test",
};
db.LogMagazines.InsertOnSubmit(lg); // Exception thrown
db.SubmitChanges();
Exception: System.InvalidOperationException: The type 'DataLayer.ILogMagazine' is not mapped as a Table..
How we can solve this problem?
The error is because you haven't applied the [Table] attribute (normally it'd go on a class type, in your case the interface type), but I don't see it working even if you did. That's how the mapping is done- when you call GetTable, it looks for the Table attribute to know where to insert/query the data from.
That said, I'm pretty sure you can't do this with an interface. The type on GetTable has to be concrete, because it uses the generic arg passed (or inferred) on GetTable to know what object to create for a query. While it might technically be able to work for inserts, the same GetTable is used for both inserts and queries- which it most certainly won't work for. Same reason XmlSerializer and DataContractSerializer don't work with interfaces.
You'll always need a concrete type, but your entity types can still implement interfaces. I'm guessing you're trying to shuttle these around somewhere (service layer, perhaps), and you'll probably need to rethink that a bit.