OData disable serialization of nullable object - c#

how can i disable serialization of null objects?
In my startup.cs i have:
services.AddControllers()
.AddJsonOptions(options =>
{
options.JsonSerializerOptions.PropertyNamingPolicy = null;
options.JsonSerializerOptions.DefaultIgnoreCondition = System.Text.Json.Serialization.JsonIgnoreCondition.WhenWritingNull;
})
.AddOData(options =>
{
options.Filter().Select().OrderBy().Count().SetMaxTop(serviceSettings.MaxPageSize).SkipToken().AddRouteComponents("api", GetEdmModel());
});
But still in result json i see "PropName": null
It is possible to use NewtonSoft in Odata?
I found someting like this Microsoft.AspNetCore.OData.NewtonsoftJson but I don't know how can I use it.

You can do it using a custom serializer:
Create a serializer:
public class IngoreNullEntityPropertiesSerializer : ODataResourceSerializer
{
public IngoreNullEntityPropertiesSerializer(ODataSerializerProvider provider)
: base(provider) { }
/// <summary>
/// Only return properties that are not null
/// </summary>
/// <param name="structuralProperty">The EDM structural property being written.</param>
/// <param name="resourceContext">The context for the entity instance being written.</param>
/// <returns>The property be written by the serilizer, a null response will effectively skip this property.</returns>
public override Microsoft.OData.ODataProperty CreateStructuralProperty(IEdmStructuralProperty structuralProperty, ResourceContext resourceContext)
{
var property = base.CreateStructuralProperty(structuralProperty, resourceContext);
return property.Value != null ? property : null;
}
}
Create a serializer provider:
public class IngoreNullEntityPropertiesSerializerProvider : ODataSerializerProvider
{
private readonly IngoreNullEntityPropertiesSerializer entityTypeSerializer;
public IngoreNullEntityPropertiesSerializerProvider(IServiceProvider rootContainer)
: base(rootContainer)
{
entityTypeSerializer = new IngoreNullEntityPropertiesSerializer(this);
}
public override IODataEdmTypeSerializer GetEdmTypeSerializer(Microsoft.OData.Edm.IEdmTypeReference edmType)
{
if (edmType.Definition.TypeKind == EdmTypeKind.Entity)
return entityTypeSerializer;
else
return base.GetEdmTypeSerializer(edmType);
}
}
Add the provider to services:
services.AddControllers().AddOData(opt => opt.Select().Expand().AddRouteComponents("odata", GetEdmModel(), conf => conf.AddSingleton<IODataSerializerProvider, IngoreNullEntityPropertiesSerializerProvider>()));
Note: I would not recommend you to do that because your response will be as anonymous. It may cause problems when you use it. Also, some of IDE which can integrate with OData will not, work properly. For example when you put data into excel it will show this error:

Related

ASP.NET Core routing - mapping only specific controllers

Per documentation it seems like it's only possible to add either single routes, one by one, or add all routes in annotated (attribute routing) controllers
DOCS: Routing to controller actions in ASP.NET Core
Is it possible to add only all routes belonging to single Controller?
Using UseEndpoints(e => e.MapControllers()) will add all controllers that are annotated, using UseEndpoints(e => e.MapControllerRoute(...)) seems to be able to add only single controller/action route, not all routes that are annotated in given controller
Sample controller:
using Microsoft.AspNetCore.Mvc;
[ApiController]
[Route("[controller]")]
public class MyApiController
{
[Route("/")]
[Route("[action]")]
[HttpGet]
public ResponseType Index()
{
// ...
}
[Route("[action]")]
public ResponseType GetListing()
{
// ...
}
}
One solution I found is to build a custom MVC feature provider and implement an extension method that allows you to specify exactly which controllers you want registered.
public static class MvcExtensions
{
/// <summary>
/// Finds the appropriate controllers
/// </summary>
/// <param name="partManager">The manager for the parts</param>
/// <param name="controllerTypes">The controller types that are allowed. </param>
public static void UseSpecificControllers(this ApplicationPartManager partManager, params Type[] controllerTypes)
{
partManager.FeatureProviders.Add(new InternalControllerFeatureProvider());
partManager.ApplicationParts.Clear();
partManager.ApplicationParts.Add(new SelectedControllersApplicationParts(controllerTypes));
}
/// <summary>
/// Only allow selected controllers
/// </summary>
/// <param name="mvcCoreBuilder">The builder that configures mvc core</param>
/// <param name="controllerTypes">The controller types that are allowed. </param>
public static IMvcCoreBuilder UseSpecificControllers(this IMvcCoreBuilder mvcCoreBuilder, params Type[] controllerTypes) => mvcCoreBuilder.ConfigureApplicationPartManager(partManager => partManager.UseSpecificControllers(controllerTypes));
/// <summary>
/// Only instantiates selected controllers, not all of them. Prevents application scanning for controllers.
/// </summary>
private class SelectedControllersApplicationParts : ApplicationPart, IApplicationPartTypeProvider
{
public SelectedControllersApplicationParts()
{
Name = "Only allow selected controllers";
}
public SelectedControllersApplicationParts(Type[] types)
{
Types = types.Select(x => x.GetTypeInfo()).ToArray();
}
public override string Name { get; }
public IEnumerable<TypeInfo> Types { get; }
}
/// <summary>
/// Ensure that internal controllers are also allowed. The default ControllerFeatureProvider hides internal controllers, but this one allows it.
/// </summary>
private class InternalControllerFeatureProvider : ControllerFeatureProvider
{
private const string ControllerTypeNameSuffix = "Controller";
/// <summary>
/// Determines if a given <paramref name="typeInfo"/> is a controller. The default ControllerFeatureProvider hides internal controllers, but this one allows it.
/// </summary>
/// <param name="typeInfo">The <see cref="TypeInfo"/> candidate.</param>
/// <returns><code>true</code> if the type is a controller; otherwise <code>false</code>.</returns>
protected override bool IsController(TypeInfo typeInfo)
{
if (!typeInfo.IsClass)
{
return false;
}
if (typeInfo.IsAbstract)
{
return false;
}
if (typeInfo.ContainsGenericParameters)
{
return false;
}
if (typeInfo.IsDefined(typeof(Microsoft.AspNetCore.Mvc.NonControllerAttribute)))
{
return false;
}
if (!typeInfo.Name.EndsWith(ControllerTypeNameSuffix, StringComparison.OrdinalIgnoreCase) &&
!typeInfo.IsDefined(typeof(Microsoft.AspNetCore.Mvc.ControllerAttribute)))
{
return false;
}
return true;
}
}
}
Put the extensions class wherever in your project, and use like this
public void ConfigureServices(IServiceCollection services)
{
// put this line before services.AddControllers()
services.AddMvcCore().UseSpecificControllers(typeof(MyApiController), typeof(MyOtherController));
}
Source: https://gist.github.com/damianh/5d69be0e3004024f03b6cc876d7b0bd3
Courtesy of Damian Hickey.

How do I resolve HttpContext from a RegisterEvent to retrieve a View in a string in asp.net core 3and Electron.net?

I've tried Dependency injection, but that always gives me a HttpContextAccessor.Current or ActionContext as null because I'm not in a request state (I think). So how can I get this context to just take a view, transform it to a html string (with Model if necessary) and throw it back in JS ? I even tried to call directly the Controller action, but it always gives HttpContext as null... I'm using Asp.NET Core 3.
Please, if someone has been going through, help me :-)
Thanks,
Edit:
I have an asp.net core based on Electron.net for a desktop application. I use a lot of IPC communication to retrieve data from backend c# using Electron.IpcMain.On. I register an action as listener in c# in a class. The main problem is that this class is really outside a normal HttpRequest or a Controller. Here is some sample code:
IpcBase Class
public abstract class IpcBase: IBaseIpcCommunicationClass
{
/// <summary>
/// Find a way to get rid of this and take more than the first window
/// </summary>
protected static BrowserWindow _mainWindow = Electron.WindowManager.BrowserWindows.First();
/// <summary>
/// Send an ipcEvent with a parameter class
/// </summary>
/// <param name="parameters">Parameters to fill</param>
public void RegisterIpcEvent<T>(IpcRegisterModel<T> registerModel) => Electron.IpcMain.On(registerModel.key, o => registerModel.action((T)
((JObject)o).ToObject(typeof(T))));
/// <summary>
/// Send a reply inside a registerevent
/// </summary>
/// <typeparam name="T">Type of model</typeparam>
/// <param name="model">model</param>
public void SendReply<T>(IpcSendParamsModel<T> model)
{
if (!string.IsNullOrEmpty(model.replyTo))
{
Electron.IpcMain.Send(_mainWindow, model.replyTo, model);
}
}
...
}
IpcUI (to get the controller view, just like an ajax call on a controller that retrieve the view in String (I already have that, but not with Ipc)
public class IpcUI: IpcBase
{
public IpcUI(IRazorViewToStringService razorViewRenderService)
{
Console.WriteLine("IpcUI::Constructor");
RegisterIpcEvent(new IpcRegisterModel<IpcSendParamsModel<AjaxPartialModel>>("renderPartial", async (param) =>
{
var param = new IpcSendParamsModel<AjaxPartialModel>("RenderPartial")
{
key = "renderPartial",
model = new AjaxPartialModel()
{
DataModel = "{items: [{\r\n MaterialIcon: \"\",\r\n Title: \"Games\",\r\n Selectable: true,\r\n Active: true,\r\n Key: \"GAMES\",\r\n BadgeCaption: \"new\",\r\n BadgeValue: \"123\",\r\n BadgeColor: \"red darken-1\",\r\n BadgePartialLink: \"\",\r\n BadgeContainerLink: \"\",\r\n BadgeModelLink: \"\",\r\n PartialLink: \"Home/Index\",\r\n ContainerLink: \"#body-content\",\r\n ModelLink: \"\"\r\n }] }".JsonDeserialize<MenuModelHeader>(),
PartialName = "PartialViews/_TopMenu"
}
};
try
{
param.results =
await razorViewRenderService.CreateAndResolveInstanceFromGeneric().RenderViewToStringAsync($"~/Views/{param.model.PartialName}.cshtml",
param.model.DataModel);
}
catch (Exception e)
{
IpcClasses.ExceptionManager.SendException(this, e, $"IpcUI params: {param.model.JsonSerialize()}");
}
}));
}
}
Razor Service (Mostly taken from here Generate string from view)
Added in startup:
services.AddHttpContextAccessor();
services.AddSingleton<IActionContextAccessor, ActionContextAccessor>();
services.AddScoped<IRazorViewToStringService, RazorRazorViewToStringService>();
When I create an instance of IpcUI, DI gives me the service, but without any HttpContext or ActionContext... Sorry for the lack of information from my last edit :-). Hope it is a bit more specific.
Oh ! I forgot something, IpcUI is created at runtime not with a new (because that don't work) but with a custom extension function that retrieves the IServiceProvider for DI:
In startup
ExtensionsUtils.ServiceProvider = app.ApplicationServices;
In ExtensionsUtils
/// <summary>
/// This is called in configure services to get the collection of services in an extension static class
/// </summary>
public static IServiceProvider ServiceProvider { get; set; }
/// <summary>
/// Create a reference from type T with DI
/// </summary>
/// <typeparam name="T"></typeparam>
/// <param name="o"></param>
/// <returns></returns>
public static T CreateAndResolveInstanceFromGeneric<T>(this T o)
{
return (T)ActivatorUtilities.CreateInstance<T>(ServiceProvider);
}
Edit 2:
I have tried to access IRazorViewToStringService from a real controller constructor and it's null again... What Am I doing wrong ???
private readonly IRazorViewToStringService _razorViewRenderService;
public BaseController(IRazorViewToStringService razorViewRenderService)
{
_razorViewRenderService = razorViewRenderService;
}
...
/// <summary>
/// Return a success http code and a View rendered as string
/// </summary>
/// <param name="ViewName">Name of MVC View (or PartialView)</param>
/// <param name="Model">Model to pass if any</param>
/// <returns>JSON: { result: "type", description: "Html Code" }</returns>
public async Task<ActionResult> CheckAndReturnView(string ViewName, object Model = null)
{
return Ok(await _razorViewRenderService.RenderViewToStringAsync(ViewName, Model));
}

How to Use Configuration with ValidateDataAnnotations

I've read the Microsoft documentation of fundamentals for Options and Configuration, but still can't find the right way to extract configuration into an object while validating data annotations.
One approach I tried in Startup.ConfigureServices
services.AddOptions<EmailConfig>().Bind(Configuration.GetSection("Email")).ValidateDataAnnotations();
This "should" allow accessing the configuration by adding this in the class constructor: (IOptions<EmailConfig> emailConfig)
However it's not working.
Another approach is to add (IConfiguration configuration) to the constructor, but this doesn't allow me to call ValidateDataAnnotations.
configuration.GetSection("Email").Get<EmailConfig>();
First question: does the responsibility to bind and validate the configuration belong to the Startup class or to the class using it? If it's used by several classes I'd say it belongs to Startup; and the class could be used in another project with different configuration layout.
Second question: what is the correct syntax to bind and validate the configuration so it can be accessed from the class?
Third question: if I'm validating through data annotations in Startup, then the class using the configuration simply assumes the configuration is valid and I don't put any re-validation whatsoever?
UPDATE: After gaining more experience and reviewing the structure of all my code, I changed my approach to follow standard patterns.
The following code DOES work... but only validates it when used. This can be registered in a class library and won't throw any errors until the particular service is used.
services.AddOptions<EmailConfig>()
.Bind(configuration.GetSection("Email"))
.ValidateDataAnnotations();
Then, in Configure, I add this to force validation of needed configuration values at startup (CheckNotNull is a custom extension method, what matters is simply that you call IOptions.Value
public void Configure(IApplicationBuilder app, IWebHostEnvironment env)
{
app?.ApplicationServices.GetService<IOptions<EmailConfig>>().Value.CheckNotNull("Config: Email");
app?.ApplicationServices.GetService<IOptions<OntraportConfig>>().Value.CheckNotNull("Config: Ontraport");
...
Then in the class using it
public class EmailService(IOptions<EmailConfig> config)
You can try validating the class yourself in start up before adding it to service collection.
Startup
var settings = Configuration.GetSection("Email").Get<EmailConfig>();
//validate
var validationResults = new List<ValidationResult>();
var validationContext = new ValidationContext(settings, serviceProvider: null, items: null);
if (!Validator.TryValidateObject(settings, validationContext, validationResults,
validateAllProperties: true)) {
//...Fail early
//will have the validation results in the list
}
services.AddSingleton(settings);
That way you are not coupled to IOptions and you also allow your code to fail early and you can explicitly inject the dependency where needed.
You could package the validation up into your own extension method like
public static T GetValid<T>(this IConfiguration configuration) {
var obj = configuration.Get<T>();
//validate
Validator.ValidateObject(obj, new ValidationContext(obj), true);
return obj;
}
for calls like
EmailConfig emailSection = Configuration.GetSection("Email").GetValid<EmailConfig>();
services.AddSingleton(emailSection);
Internally, ValidateDataAnnotations is basically doing the same thing.
/// <summary>
/// Validates a specific named options instance (or all when name is null).
/// </summary>
/// <param name="name">The name of the options instance being validated.</param>
/// <param name="options">The options instance.</param>
/// <returns>The <see cref="ValidateOptionsResult"/> result.</returns>
public ValidateOptionsResult Validate(string name, TOptions options)
{
// Null name is used to configure all named options.
if (Name == null || name == Name)
{
var validationResults = new List<ValidationResult>();
if (Validator.TryValidateObject(options,
new ValidationContext(options, serviceProvider: null, items: null),
validationResults,
validateAllProperties: true))
{
return ValidateOptionsResult.Success;
}
return ValidateOptionsResult.Fail(String.Join(Environment.NewLine,
validationResults.Select(r => "DataAnnotation validation failed for members " +
String.Join(", ", r.MemberNames) +
" with the error '" + r.ErrorMessage + "'.")));
}
// Ignored if not validating this instance.
return ValidateOptionsResult.Skip;
}
Source Code
Update from the Future
Newer versions of .NET added more extension methods to simplify this.
Note: Technically these are all from Microsoft.Extensions.XYZ packages released alongside .NET. It's possible that these packages are compatible with earlier versions of .NET as well, but I haven't verified backward-compatibility.
OP's Example
services.AddOptions<EmailConfig>()
.Bind(configuration.GetSection("Email"))
.ValidateDataAnnotations();
Can now be simplified to:
// Requires .NET 5 extensions or greater
services.AddOptions<EmailConfig>()
.BindConfiguration("Email")
.ValidateDataAnnotations();
...and for eager validation at startup (rather than when options are used), we can add a single line:
// Requires .NET 6 extensions or greater
services.AddOptions<EmailConfig>()
.BindConfiguration("Email")
.ValidateDataAnnotations()
.ValidateOnStart();
Source/Credit
I learned about these updates from Andrew Lock's blog post. Credit and thanks go to him: Adding validation to strongly typed configuration objects in .NET 6
There is still no answer as to how ValidateDataAnnotations work, but based on Nkosi's answer, I wrote this class extension to easily run the validation on-demand. Because it's an extension on Object, I put it into a sub-namespace to only enable it when needed.
namespace Websites.Business.Validation {
/// <summary>
/// Provides methods to validate objects based on DataAnnotations.
/// </summary>
public static class ValidationExtensions {
/// <summary>
/// Validates an object based on its DataAnnotations and throws an exception if the object is not valid.
/// </summary>
/// <param name="obj">The object to validate.</param>
public static T ValidateAndThrow<T>(this T obj) {
Validator.ValidateObject(obj, new ValidationContext(obj), true);
return obj;
}
/// <summary>
/// Validates an object based on its DataAnnotations and returns a list of validation errors.
/// </summary>
/// <param name="obj">The object to validate.</param>
/// <returns>A list of validation errors.</returns>
public static ICollection<ValidationResult> Validate<T>(this T obj) {
var Results = new List<ValidationResult>();
var Context = new ValidationContext(obj);
if (!Validator.TryValidateObject(obj, Context, Results, true))
return Results;
return null;
}
}
}
Then in Startup it's quite straightforward
EmailConfig EmailSection = Configuration.GetSection("Email").Get<EmailConfig>().ValidateAndThrow();
services.AddSingleton<EmailConfig>(EmailSection);
Works like a charm; actually works like I'd expect ValidateDataAnnotations to work.
You can also use a method to validate all IOptions in your IOC conainter
private void CheckConfiguration(IApplicationBuilder app, IServiceCollection services)
{
var optionsServiceDescriptors = services.Where(s => s.ServiceType.Name.Contains("IOptionsChangeTokenSource"));
foreach (var service in optionsServiceDescriptors)
{
var genericTypes = service.ServiceType.GenericTypeArguments;
if (genericTypes.Length > 0)
{
var optionsType = genericTypes[0];
var genericOptions = typeof(IOptions<>).MakeGenericType(optionsType);
dynamic instance = app.ApplicationServices.GetService(genericOptions);
var options = instance.Value;
var results = new List<ValidationResult>();
var isValid = Validator.TryValidateObject(options, new ValidationContext(options), results, true);
if (!isValid)
{
var messages = new List<string> { "Configuration issues" };
messages.AddRange(results.Select(r => r.ErrorMessage));
throw new Exception(string.Join("\n", messages));
}
}
}
}
You can find a example here : https://github.com/michelcedric/GetRequiredSectionSample/blob/feature/add-check-configuration/GetRequiredSectionSample/Startup.cs#L73

WebApi: Creating custom BadRequest status outside of ApiController

Using .NET Core 2.0 WebApi.
I have a webapi which has many endpoints where each endpoint is handling and throwing BadRequest when it fails. As below:
if(data == null)
{
return BadRequest("Data must not be blank.");
}
Now since these status codes are repetitive in my api, I was thinking to create a Helper method which would return BadRequest back to my API.
So I created a static helper class. But the issue here is BadRequest is part of ControllerBase and is not available in my helper class. What is the best way to create this method that would return a BadRequest.
--Updated---
I want something like this:
public static BadRequest GetBadRequestMessage(string message)
{
return BadRequest(message);
}
I have also tried as:
public static BadRequestResult GetBadRequestMessage(string message)
{
return new BadRequestResult(message);
}
But this gives error: Severity Code Description Project File Line Suppression State
Error CS1729 'BadRequestResult' does not contain a constructor that takes 1 arguments
You can manually initialize the action result
return new BadRequestObjectResult("error message here");
Which is basically what the ControllerBase does internally
/// <summary>
/// Creates an <see cref="BadRequestResult"/> that produces a <see cref="StatusCodes.Status400BadRequest"/> response.
/// </summary>
/// <returns>The created <see cref="BadRequestResult"/> for the response.</returns>
[NonAction]
public virtual BadRequestResult BadRequest()
=> new BadRequestResult();
/// <summary>
/// Creates an <see cref="BadRequestObjectResult"/> that produces a <see cref="StatusCodes.Status400BadRequest"/> response.
/// </summary>
/// <param name="error">An error object to be returned to the client.</param>
/// <returns>The created <see cref="BadRequestObjectResult"/> for the response.</returns>
[NonAction]
public virtual BadRequestObjectResult BadRequest(object error)
=> new BadRequestObjectResult(error);
/// <summary>
/// Creates an <see cref="BadRequestObjectResult"/> that produces a <see cref="StatusCodes.Status400BadRequest"/> response.
/// </summary>
/// <param name="modelState">The <see cref="ModelStateDictionary" /> containing errors to be returned to the client.</param>
/// <returns>The created <see cref="BadRequestObjectResult"/> for the response.</returns>
[NonAction]
public virtual BadRequestObjectResult BadRequest(ModelStateDictionary modelState)
{
if (modelState == null)
{
throw new ArgumentNullException(nameof(modelState));
}
return new BadRequestObjectResult(modelState);
}
Source
In .net core 5 project create BadRequestConfig class in your ioc folder/class library :
using Microsoft.AspNetCore.Mvc;
using Microsoft.Extensions.DependencyInjection;
using System.Linq;
namespace Ioc
{
public static class BadRequestConfig
{
// error 400 handling - remove extra fields in error model - remove if(ModelState.IsValid)
public static IMvcBuilder AddBadRequestServices(this IMvcBuilder services)
{
services.ConfigureApiBehaviorOptions(options =>
options.InvalidModelStateResponseFactory = actionContext =>
{
var modelState = actionContext.ModelState.Values;
var allErrors = actionContext.ModelState.Values.SelectMany(v => v.Errors);
return new BadRequestObjectResult(new
{
StatusCode = 400,
Message = string.Join(" - ", allErrors.Select(e => e.ErrorMessage))
});
});
return services;
}
}
}
Then add AddBadRequestServices() method in ConfigureServices method in your startup.cs file :
public void ConfigureServices(IServiceCollection services)
{
services.AddDbServices();
services.AddAppServices();
services.AddControllers().AddBadRequestServices(); // here
services.AddJwtAuthentication();
services.AddSwaggerServices();
}
By this solution it is not necessary to write if(ModelState.IsValid) in your actions.

Can I use the dynamic feature of Profile inside a MVC Controller?

I suspect this only applies to asp.net pages, but according to this:
http://msdn.microsoft.com/en-us/library/2y3fs9xs.aspx
I can define properties in web.config like so:
<profile>
<properties>
<add name="PostalCode" />
</properties>
</profile>
Then go ahead and use them like this:
Profile.PostalCode = txtPostalCode.Text;
But this does not compile for me inside a Controller:
public ActionResult Index()
{
Profile.PostalCode = "codeofpost";
return View();
}
Profile is of type ProfileBase and not dynamic, so I've no idea how this would work, but the document says otherwise.
Profile class is generated only in ASP.NET Website project NOT in ASP.NET Web aplication.
In Web Application project you need to use
ProfielBase.GetPropertyValue(PropertyName);
References: http://weblogs.asp.net/anasghanem/archive/2008/04/12/the-differences-in-profile-between-web-application-projects-wap-and-website.aspx
As I've been told it's not possible, I've decided to use a dynamic to do this for myself. I guess it's just syntactic sugar in the end.
Descending from this enables Profile.PostalCode = "codeofpost";
/// <summary>
/// Provides a dynamic Profile.
/// </summary>
public abstract class ControllerBase : Controller
{
private readonly Lazy<DynamicProfile> _dProfile;
protected ControllerBase()
{
_dProfile = new Lazy<DynamicProfile>(() => new DynamicProfile(base.Profile));
}
private sealed class DynamicProfile : DynamicObject
{
private readonly ProfileBase _profile;
public DynamicProfile(ProfileBase profile)
{
_profile = profile;
}
public override bool TryGetMember(GetMemberBinder binder, out object result)
{
result = _profile.GetPropertyValue(binder.Name);
return true;
}
public override bool TrySetMember(SetMemberBinder binder, object value)
{
_profile.SetPropertyValue(binder.Name, value);
_profile.Save();
return true;
}
}
/// <summary>
/// New dynamic profile, can now access the properties as though they are on the Profile,
/// e.g. Profile.PostCode
/// </summary>
protected new dynamic Profile
{
get { return _dProfile.Value; }
}
/// <summary>
/// Provides access to the original profile object.
/// </summary>
protected ProfileBase ProfileBase
{
get { return base.Profile; }
}
}

Categories