In my SQL database I have stored procedures and functions that take table valued parameters. I can create the table valued parameter and populate it from any List of entities of type T with pure C# like this:
DataTable table = new DataTable();
var props = typeof(T).GetProperties();
var columns = props.Select(p => new DataColumn(p.Name, p.PropertyType));
table.Columns.AddRange(columns.ToArray());
List<T> entities = GetEntities();
foreach (var entity in entities)
{
DataRow row = table.NewRow();
foreach (var prop in props)
{
row[prop.Name] = prop.GetValue(entity);
}
table.Rows.Add(row);
}
var tvp = new SqlParameter("#Entities", table) { TypeName = "dbo.T", SqlDbType = SqlDbType.Structured };
But in order to pass the above TVP to the stored procedure one must create a corresponding user-defined type T in sql server first. So far I am unable to find a way to do this without using raw SQL. Like this:
-- I want to avoid this
CREATE TYPE [dbo].[T] AS TABLE(
[Id] [INT] NOT NULL,
[Name] [varchar](255) NULL,
)
Is there a way to define the SQL user-defined list from the C# type T without having to write SQL? Somewhere there are already libraries that map C# to SQL types, I don't want to reinvent the wheel and write SQL code that is difficult to maintain and can easily get out of sync with the C# class.
After hours of research I reached the same conclusion suggested by David Browne, it can't be done natively.
However not all is lost, I managed to extend the default EF Core SQl Generator to allow me to manually create and drop user defined table types in migrations using the same pure C# syntax for creating and dropping tables, without mentioning SQL datatypes (e.g nvarchar). For example in a migration file:
migrationBuilder.CreateUserDefinedTableType(
name: "T",
schema: "dto",
columns: udt => new
{
// Example columns
Id = udt.Column<int>(nullable: false),
Date = udt.Column<DateTime>(nullable: false),
Memo = udt.Column<string>(maxLength: 256, nullable: true)
}
);
I am sharing the code below:
/// <summary>
/// A <see cref="MigrationOperation"/> for creating a new user-defined table type
/// </summary>
public class CreateUserDefinedTableTypeOperation : MigrationOperation
{
/// <summary>
/// The name of the user defined table type.
/// </summary>
public virtual string Name { get; set; }
/// <summary>
/// The schema that contains the user defined table type, or <c>null</c> if the default schema should be used.
/// </summary>
public virtual string Schema { get; set; }
/// <summary>
/// An ordered list of <see cref="AddColumnOperation" /> for adding columns to the user defined list.
/// </summary>
public virtual List<AddColumnOperation> Columns { get; } = new List<AddColumnOperation>();
}
/// <summary>
/// A <see cref="MigrationOperation"/> for dropping an existing user-defined table type
/// </summary>
public class DropUserDefinedTableTypeOperation : MigrationOperation
{
/// <summary>
/// The name of the user defined table type.
/// </summary>
public virtual string Name { get; set; }
/// <summary>
/// The schema that contains the user defined table type, or <c>null</c> if the default schema should be used.
/// </summary>
public virtual string Schema { get; set; }
}
/// <summary>
/// A builder for <see cref="CreateUserDefinedTableTypeOperation" /> operations.
/// </summary>
/// <typeparam name="TColumns"> Type of a typically anonymous type for building columns. </typeparam>
public class UserDefinedTableTypeColumnsBuilder
{
private readonly CreateUserDefinedTableTypeOperation _createTableOperation;
/// <summary>
/// Constructs a builder for the given <see cref="CreateUserDefinedTableTypeOperation" />.
/// </summary>
/// <param name="createUserDefinedTableTypeOperation"> The operation. </param>
public UserDefinedTableTypeColumnsBuilder(CreateUserDefinedTableTypeOperation createUserDefinedTableTypeOperation)
{
_createTableOperation = createUserDefinedTableTypeOperation ??
throw new ArgumentNullException(nameof(createUserDefinedTableTypeOperation));
}
public virtual OperationBuilder<AddColumnOperation> Column<T>(
string type = null,
bool? unicode = null,
int? maxLength = null,
bool rowVersion = false,
string name = null,
bool nullable = false,
object defaultValue = null,
string defaultValueSql = null,
string computedColumnSql = null,
bool? fixedLength = null)
{
var operation = new AddColumnOperation
{
Schema = _createTableOperation.Schema,
Table = _createTableOperation.Name,
Name = name,
ClrType = typeof(T),
ColumnType = type,
IsUnicode = unicode,
MaxLength = maxLength,
IsRowVersion = rowVersion,
IsNullable = nullable,
DefaultValue = defaultValue,
DefaultValueSql = defaultValueSql,
ComputedColumnSql = computedColumnSql,
IsFixedLength = fixedLength
};
_createTableOperation.Columns.Add(operation);
return new OperationBuilder<AddColumnOperation>(operation);
}
}
/// <summary>
/// An extended version of the default <see cref="SqlServerMigrationsSqlGenerator"/>
/// which adds functionality for creating and dropping User-Defined Table Types of SQL
/// server inside migration files using the same syntax as creating and dropping tables,
/// to use this generator, register it using <see cref="DbContextOptionsBuilder.ReplaceService{ISqlMigr, TImplementation}"/>
/// in order to replace the default implementation of <see cref="IMigrationsSqlGenerator"/>
/// </summary>
public class CustomSqlServerMigrationsSqlGenerator : SqlServerMigrationsSqlGenerator
{
public CustomSqlServerMigrationsSqlGenerator(
MigrationsSqlGeneratorDependencies dependencies,
IMigrationsAnnotationProvider migrationsAnnotations) : base(dependencies, migrationsAnnotations)
{
}
protected override void Generate(
MigrationOperation operation,
IModel model,
MigrationCommandListBuilder builder)
{
if (operation is CreateUserDefinedTableTypeOperation createUdtOperation)
{
GenerateCreateUdt(createUdtOperation, model, builder);
}
else if(operation is DropUserDefinedTableTypeOperation dropUdtOperation)
{
GenerateDropUdt(dropUdtOperation, builder);
}
else
{
base.Generate(operation, model, builder);
}
}
private void GenerateCreateUdt(
CreateUserDefinedTableTypeOperation operation,
IModel model,
MigrationCommandListBuilder builder)
{
builder
.Append("CREATE TYPE ")
.Append(Dependencies.SqlGenerationHelper.DelimitIdentifier(operation.Name, operation.Schema))
.AppendLine(" AS TABLE (");
using (builder.Indent())
{
for (var i = 0; i < operation.Columns.Count; i++)
{
var column = operation.Columns[i];
ColumnDefinition(column, model, builder);
if (i != operation.Columns.Count - 1)
{
builder.AppendLine(",");
}
}
builder.AppendLine();
}
builder.Append(")");
builder.AppendLine(Dependencies.SqlGenerationHelper.StatementTerminator).EndCommand();
}
private void GenerateDropUdt(
DropUserDefinedTableTypeOperation operation,
MigrationCommandListBuilder builder)
{
builder
.Append("DROP TYPE ")
.Append(Dependencies.SqlGenerationHelper.DelimitIdentifier(operation.Name, operation.Schema))
.AppendLine(Dependencies.SqlGenerationHelper.StatementTerminator)
.EndCommand();
}
}
public static class MigrationBuilderExtensions
{
/// <summary>
/// Builds an <see cref="CreateUserDefinedTableTypeOperation" /> to create a new user-defined table type.
/// </summary>
/// <typeparam name="TColumns"> Type of a typically anonymous type for building columns. </typeparam>
/// <param name="name"> The name of the user-defined table type. </param>
/// <param name="columns">
/// A delegate using a <see cref="ColumnsBuilder" /> to create an anonymous type configuring the columns of the user-defined table type.
/// </param>
/// <param name="schema"> The schema that contains the user-defined table type, or <c>null</c> to use the default schema. </param>
/// <returns> A builder to allow annotations to be added to the operation. </returns>
public static MigrationBuilder CreateUserDefinedTableType<TColumns>(
this MigrationBuilder builder,
string name,
Func<UserDefinedTableTypeColumnsBuilder, TColumns> columns,
string schema = null)
{
var createUdtOperation = new CreateUserDefinedTableTypeOperation
{
Name = name,
Schema = schema
};
var columnBuilder = new UserDefinedTableTypeColumnsBuilder(createUdtOperation);
var columnsObject = columns(columnBuilder);
var columnMap = new Dictionary<PropertyInfo, AddColumnOperation>();
foreach (var property in typeof(TColumns).GetTypeInfo().DeclaredProperties)
{
var addColumnOperation = ((IInfrastructure<AddColumnOperation>)property.GetMethod.Invoke(columnsObject, null)).Instance;
if (addColumnOperation.Name == null)
{
addColumnOperation.Name = property.Name;
}
columnMap.Add(property, addColumnOperation);
}
builder.Operations.Add(createUdtOperation);
return builder;
}
/// <summary>
/// Builds an <see cref="DropUserDefinedTableTypeOperation" /> to drop an existing user-defined table type.
/// </summary>
/// <param name="name"> The name of the user-defined table type to drop. </param>
/// <param name="schema"> The schema that contains the user-defined table type, or <c>null</c> to use the default schema. </param>
/// <returns> A builder to allow annotations to be added to the operation. </returns>
public static MigrationBuilder DropUserDefinedTableType(
this MigrationBuilder builder,
string name,
string schema = null)
{
builder.Operations.Add(new DropUserDefinedTableTypeOperation
{
Name = name,
Schema = schema
});
return builder;
}
}
Before migrations can work with the above code, you need to replace the service in the DbContextOptions, in your Startup's Configure services (with ASP.NET Core) like this:
services.AddDbContext<MyContext>(opt =>
opt.UseSqlServer(_config.GetConnectionString("MyContextConnection"))
.ReplaceService<IMigrationsSqlGenerator, CustomSqlServerMigrationsSqlGenerator>());
Relevant links:
https://learn.microsoft.com/en-us/ef/core/managing-schemas/migrations/operations
https://github.com/aspnet/EntityFrameworkCore/blob/release/2.1/src/EFCore.SqlServer/Migrations/SqlServerMigrationsSqlGenerator.cs
I don't know of anything that generates the User Defined Table Types for an Entity. You already have to keep your C# classes in sync with the database tables, so you would tack the Table Type generation process onto that.
An alternative is to pass the data to SQL Server using JSON instead of a TVP. EG: How to Write In Clause with EF FromSql?
In Summary: I Want to replace IFormFile IFormFileCollection with my own classes not attached to Asp .Net because my view models are in different project with poco classes. My custom classes are ICommonFile, ICommonFileCollection, IFormFile (not Asp .net core class) and IFormFileCollection.
i will share it here:
ICommonFile.cs
/// <summary>
/// File with common Parameters including bytes
/// </summary>
public interface ICommonFile
{
/// <summary>
/// Stream File
/// </summary>
Stream File { get; }
/// <summary>
/// Name of the file
/// </summary>
string Name { get; }
/// <summary>
/// Gets the file name with extension.
/// </summary>
string FileName { get; }
/// <summary>
/// Gets the file length in bytes.
/// </summary>
long Length { get; }
/// <summary>
/// Copies the contents of the uploaded file to the <paramref name="target"/> stream.
/// </summary>
/// <param name="target">The stream to copy the file contents to.</param>
void CopyTo(Stream target);
/// <summary>
/// Asynchronously copies the contents of the uploaded file to the <paramref name="target"/> stream.
/// </summary>
/// <param name="target">The stream to copy the file contents to.</param>
/// <param name="cancellationToken">Enables cooperative cancellation between threads</param>
Task CopyToAsync(Stream target, CancellationToken cancellationToken = default(CancellationToken));
}
ICommonFileCollection.cs
/// <inheritdoc />
/// <summary>
/// Represents the collection of files.
/// </summary>
public interface ICommonFileCollection : IReadOnlyList<ICommonFile>
{
/// <summary>
/// File Indexer by name
/// </summary>
/// <param name="name">File name index</param>
/// <returns>File with related file name index</returns>
ICommonFile this[string name] { get; }
/// <summary>
/// Gets file by name
/// </summary>
/// <param name="name">file name</param>
/// <returns>File with related file name index</returns>
ICommonFile GetFile(string name);
/// <summary>
/// Gets Files by name
/// </summary>
/// <param name="name"></param>>
/// <returns>Files with related file name index</returns>
IReadOnlyList<ICommonFile> GetFiles(string name);
}
IFormFile.cs
/// <inheritdoc />
/// <summary>
/// File transferred by HttpProtocol, this is an independent
/// Asp.net core interface
/// </summary>
public interface IFormFile : ICommonFile
{
/// <summary>
/// Gets the raw Content-Type header of the uploaded file.
/// </summary>
string ContentType { get; }
/// <summary>
/// Gets the raw Content-Disposition header of the uploaded file.
/// </summary>
string ContentDisposition { get; }
}
IFormFileCollection.cs
/// <summary>
/// File Collection transferred by HttpProtocol, this is an independent
/// Asp.net core implementation
/// </summary>
public interface IFormFileCollection
{
//Use it when you need to implement new features to Form File collection over HttpProtocol
}
I finally created my model binders successfully, i will share it too:
FormFileModelBinderProvider.cs
/// <inheritdoc />
/// <summary>
/// Model Binder Provider, it inspects
/// any model when the request is triggered
/// </summary>
public class FormFileModelBinderProvider : IModelBinderProvider
{
/// <inheritdoc />
/// <summary>
/// Inspects a Model for any CommonFile class or Collection with
/// same class if exist the FormFileModelBinder initiates
/// </summary>
/// <param name="context">Model provider context</param>
/// <returns>a new Instance o FormFileModelBinder if type is found otherwise null</returns>
public IModelBinder GetBinder(ModelBinderProviderContext context)
{
if (context == null) throw new ArgumentNullException(nameof(context));
if (!context.Metadata.IsComplexType) return null;
var isSingleCommonFile = IsSingleCommonFile(context.Metadata.ModelType);
var isCommonFileCollection = IsCommonFileCollection(context.Metadata.ModelType);
if (!isSingleCommonFile && !isCommonFileCollection) return null;
return new FormFileModelBinder();
}
/// <summary>
/// Checks if object type is a CommonFile Collection
/// </summary>
/// <param name="modelType">Context Meta data ModelType</param>
/// <returns>If modelType is a collection of CommonFile returns true otherwise false</returns>
private static bool IsCommonFileCollection(Type modelType)
{
if (typeof(ICommonFileCollection).IsAssignableFrom(modelType))
{
return true;
}
var hasCommonFileArguments = modelType.GetGenericArguments()
.AsParallel().Any(t => typeof(ICommonFile).IsAssignableFrom(t));
if (typeof(IEnumerable).IsAssignableFrom(modelType) && hasCommonFileArguments)
{
return true;
}
if (typeof(IAsyncEnumerable<object>).IsAssignableFrom(modelType) && hasCommonFileArguments)
{
return true;
}
return false;
}
/// <summary>
/// Checks if object type is CommonFile or an implementation of ICommonFile
/// </summary>
/// <param name="modelType"></param>
/// <returns></returns>
private static bool IsSingleCommonFile(Type modelType)
{
if (modelType == typeof(ICommonFile) || modelType.GetInterfaces().Contains(typeof(ICommonFile)))
{
return true;
}
return false;
}
}
FormFileModelBinder.cs
/// <inheritdoc />
/// <summary>
/// Form File Model binder
/// Parses the Form file object type to a commonFile
/// </summary>
public class FormFileModelBinder : IModelBinder
{
/// <summary>
/// Expression to map IFormFile object type to CommonFile
/// </summary>
private readonly Func<Microsoft.AspNetCore.Http.IFormFile, ICommonFile> _expression;
/// <summary>
/// FormFile Model binder constructor
/// </summary>
public FormFileModelBinder()
{
_expression = x => new CommonFile(x.OpenReadStream(), x.Length, x.Name, x.FileName);
}
/// <inheritdoc />
/// <summary>
/// It Binds IFormFile to Common file, getting the file
/// from the binding context
/// </summary>
/// <param name="bindingContext">Http Context</param>
/// <returns>Completed Task</returns>
// TODO: Bind this context to ICommonFile or ICommonFileCollection object
public Task BindModelAsync(ModelBindingContext bindingContext)
{
dynamic model;
if (bindingContext == null) throw new ArgumentNullException(nameof(bindingContext));
var formFiles = bindingContext.ActionContext.HttpContext.Request.Form.Files;
if (!formFiles.Any()) return Task.CompletedTask;
if (formFiles.Count > 1)
{
model = formFiles.AsParallel().Select(_expression);
}
else
{
model = new FormFileCollection();
model.AddRange(filteredFiles.AsParallel().Select(_expression));
}
bindingContext.Result = ModelBindingResult.Success(model);
return Task.CompletedTask;
}
}
Actually Everything is working good except when i have Nested Models. I share an example of my models I'm using and I'll do some comments with working scenarios and which don't
Test.cs
public class Test
{
//It's Working
public ICommonFileCollection Files { get; set; }
//It's Working
public ICommonFileCollection Files2 { get; set; }
//This is a nested model
public TestExtra TestExtra { get; set; }
}
TestExtra.cs
public class TestExtra
{
//It's not working
public ICommonFileCollection Files { get; set; }
}
Actually when i make a request to my API I've got the following (Screenshot):
I'm sharing a screenshot of my postman request too for clarifying my request is good.
If there is any subjection to make this work with nested model it would be great.
Asp Net Core Model Binder won't bind model with one property only, if you have one property in a class, that property will be null but when you add two or more will bind it. my mistake i had one one property in a nested class. The entire code is correct.
I need create XML comment for return value of method Login. This method returns object type
of Result. This object contains session id if login was successful and if login was unsuccessful contains error message.
I don’t know how decribe this in XML comment.
/// <summary>
/// Login to server
/// </summary>
/// <param name="name">Name of user</param>
/// <param name="password">User password</param>
/// <returns>
/// This method return object type of Result<Account>
/// If login was successful contains sessions
/// If login was unsuccessful contains error
/// </returns>
Result<Account> Login(string name, string password);
/// <summary>
/// Return value of method
/// </summary>
/// <typeparam name="T">Type of return value</typeparam>
public class Result<T>
{
/// <summary>
/// Return message
/// </summary>
public string ResultMessage { get; set; }
/// <summary>
/// Return value
/// </summary>
public T ReturnValue { get; set; }
}
public class Account
{
public string Name{get;set;}
public string Password {get;set;}
public string Session {get;set;
}
Thank you for advices.
You would use:
/// ...
/// This method returns an object of type <see cref="Result{Account}"/>.
/// ...
See cref Attribute.
I am trying to implement elasticsearch into my webshop but having some troubles on using filters. The filtering is done dynamically.
Example:
I start with showing all the products that are indexed. So no filter is applied. Visitors can choose their own filters like: color, size, brand, type, category, ....
But I don't now how to build the search result with elasticsearch and NEST.
This is my solution without filtering:
var query = ElasticClient.Search<Product>(s => s
.From(from)
.Size(size)
);
I also have another question on indexing a collection<> or list<>. I had to use JsonIgnore on those collections. Could I index those as well?
This is my class:
/// <summary>
/// Represents a product
/// </summary>
public partial class Product {
private ICollection<ProductCategory> _productCategories;
private ICollection<ProductManufacturer> _productManufacturers;
private ICollection<ProductPicture> _productPictures;
/// <summary>
/// Gets or sets the name
/// </summary>
public virtual string Name { get; set; }
/// <summary>
/// Gets or sets the short description
/// </summary>
public virtual string ShortDescription { get; set; }
/// <summary>
/// Gets or sets a value indicating whether the entity is published
/// </summary>
public virtual bool Published { get; set; }
/// <summary>
/// Gets or sets a value indicating whether the entity has been deleted
/// </summary>
public virtual bool Deleted { get; set; }
/// <summary>
/// Gets or sets the date and time of product creation
/// </summary>
public virtual DateTime CreatedOnUtc { get; set; }
/// <summary>
/// Gets or sets the date and time of product update
/// </summary>
public virtual DateTime UpdatedOnUtc { get; set; }
/// <summary>
/// Gets or sets the collection of ProductCategory
/// </summary>
[JsonIgnore] /* added - wesley */
public virtual ICollection<ProductCategory> ProductCategories
{
get { return _productCategories ?? (_productCategories = new List<ProductCategory>()); }
protected set { _productCategories = value; }
}
/// <summary>
/// Gets or sets the collection of ProductManufacturer
/// </summary>
[JsonIgnore] /* added - wesley */
public virtual ICollection<ProductManufacturer> ProductManufacturers
{
get { return _productManufacturers ?? (_productManufacturers = new List<ProductManufacturer>()); }
protected set { _productManufacturers = value; }
}
/// <summary>
/// Gets or sets the collection of ProductPicture
/// </summary>
[JsonIgnore] /* added - wesley */
public virtual ICollection<ProductPicture> ProductPictures
{
get { return _productPictures ?? (_productPictures = new List<ProductPicture>()); }
protected set { _productPictures = value; }
}
}
Is there someone who can help me?
Be sure to read the whole documentation on writing queries here: http://nest.azurewebsites.net/nest/writing-queries.html
What follows is a pasted excerpt from there.
Conditionless queries
Writing complex boolean queries is one thing but more often then not you'll want to make decisions on how to query based on user input.
public class UserInput
{
public string Name { get; set; }
public string FirstName { get; set; }
public int? LOC { get; set; }
}
and then
.Query(q=> {
QueryDescriptor<ElasticSearch> query = null;
if (!string.IsNullOrEmpty(userInput.Name))
query &= q.Term(p=>p.Name, userInput.Name);
if (!string.IsNullOrEmpty(userInput.FirstName))
query &= q
.Term("followers.firstName", userInput.FirstName);
if (userInput.LOC.HasValue)
query &= q.Range(r=>r.OnField(p=>p.Loc).From(userInput.Loc.Value))
return query;
})
This again turns tedious and verbose rather quickly too. Therefor nest allows you to write the previous query as:
.Query(q=>
q.Term(p=>p.Name, userInput.Name);
&& q.Term("followers.firstName", userInput.FirstName)
&& q.Range(r=>r.OnField(p=>p.Loc).From(userInput.Loc))
)
If any of the queries would result in an empty query they won't be sent to elasticsearch.
So if all the terms are null (or empty string) on userInput except userInput.Loc it wouldn't even wrap the range query in a boolean query but just issue a plain range query.
If all of them empty it will result in a match_all query.
This conditionless behavior is turned on by default but can be turned of like so:
var result = client.Search<ElasticSearchProject>(s=>s
.From(0)
.Size(10)
.Strict() //disable conditionlessqueries by default
///EXAMPLE HERE
);
However queries themselves can opt back in or out.
.Query(q=>
q.Strict().Term(p=>p.Name, userInput.Name);
&& q.Term("followers.firstName", userInput.FirstName)
&& q.Strict(false).Range(r=>r.OnField(p=>p.Loc).From(userInput.Loc))
)
In this example if userInput.Name is null or empty it will result in a DslException. The range query will use conditionless logic no matter if the SearchDescriptor uses .Strict() or not.
Also good to note is that conditionless query logic propagates:
q.Strict().Term(p=>p.Name, userInput.Name);
&& q.Term("followers.firstName", userInput.FirstName)
&& q.Filtered(fq => fq
.Query(qff =>
qff.Terms(p => p.Country, userInput.Countries)
&& qff.Terms(p => p.Loc, userInput.Loc)
)
)
If both userInput.Countries and userInput.Loc are null or empty the entire filtered query will be not be issued.
What cool functionality and methods do you add to your ASP.net BasePage : System.Web.UI.Page classes?
Examples
Here's something I use for authentication, and I'd like to hear your opinions on this:
protected override void OnPreInit(EventArgs e)
{
base.OnPreInit(e);
// Authentication code omitted... Essentially same as below.
if (_RequiresAuthentication && !(IsAuthorized))
{
RespondForbidden("You do not have permissions to view this page.", UnauthorizedRedirect);
return;
}
}
// This function is overridden in each page subclass and fitted to each page's
// own authorization requirements.
// This also allows cascading authorization checks,
// e.g: User has permission to view page? No - base.IsAuthorized - Is user an admin?
protected virtual bool IsAuthorized
{
get { return true; }
}
My BasePage class contains an instance of this class:
public class StatusCodeResponse {
public StatusCodeResponse(HttpContext context) {
this._context = context;
}
/// <summary>
/// Responds with a specified status code, and if specified - transfers to a page.
/// </summary>
private void RespondStatusCode(HttpContext context, System.Net.HttpStatusCode status, string message, string transfer)
{
if (string.IsNullOrEmpty(transfer))
{
throw new HttpException((int)status, message);
}
context.Response.StatusCode = (int)status;
context.Response.StatusDescription = message;
context.Server.Transfer(transfer);
}
public void RespondForbidden(string message, string transfer)
{
RespondStatusCode(this._context, System.Net.HttpStatusCode.Forbidden, message, transfer);
}
// And a few more like these...
}
As a side note, this could be accomplished using extension methods for the HttpResponse object.
And another method I find quite handy for parsing querystring int arguments:
public bool ParseId(string field, out int result)
{
return (int.TryParse(Request.QueryString[field], out result) && result > 0);
}
Session related stuff, some complex object in the BasePage that maps to a session, and expose it as a property.
Doing stuff like filling a crumble pad object.
But most important: do not make your basepage into some helper class. Don't add stuff like ParseId(), that's just ridiculous.
Also, based on the first post: make stuff like IsAuthorized abstract. This way you don't create giant security holes if someone forgets that there is some virtual method.
using System;
using System.Collections.Generic;
using System.Text.RegularExpressions;
using System.Web;
using System.Web.UI;
using System.Web.UI.HtmlControls;
using System.Web.UI.WebControls;
namespace MySite
{
/// <summary>
/// Base class with properties for meta tags for content pages
/// http://www.codeproject.com/KB/aspnet/PageTags.aspx
/// http://weblogs.asp.net/scottgu/archive/2005/08/02/421405.aspx
/// </summary>
public partial class BasePage : System.Web.UI.Page
{
private string keywords;
private string description;
/// <SUMMARY>
/// Gets or sets the Meta Keywords tag for the page
/// </SUMMARY>
public string Meta_Keywords
{
get
{
return keywords;
}
set
{
// Strip out any excessive white-space, newlines and linefeeds
keywords = Regex.Replace(value, "\\s+", " ");
}
}
/// <SUMMARY>
/// Gets or sets the Meta Description tag for the page
/// </SUMMARY>
public string Meta_Description
{
get
{
return description;
}
set
{
// Strip out any excessive white-space, newlines and linefeeds
description = Regex.Replace(value, "\\s+", " ");
}
}
// Constructor
// Add an event handler to Init event for the control
// so we can execute code when a server control (page)
// that inherits from this base class is initialized.
public BasePage()
{
Init += new EventHandler(BasePage_Init);
}
// Whenever a page that uses this base class is initialized,
// add meta keywords and descriptions if available
void BasePage_Init(object sender, EventArgs e)
{
if (!String.IsNullOrEmpty(Meta_Keywords))
{
HtmlMeta tag = new HtmlMeta();
tag.Name = "keywords";
tag.Content = Meta_Keywords;
Header.Controls.Add(tag);
}
if (!String.IsNullOrEmpty(Meta_Description))
{
HtmlMeta tag = new HtmlMeta();
tag.Name = "description";
tag.Content = Meta_Description;
Header.Controls.Add(tag);
}
}
}
}
Along with the metadata already mentioned (mostly obsolete in ASP.NET 4.0 with the new Page.MetaDescription and Page.MetaKeywords properties), I've also had methods to add other header links to my page such as specific ones for adding page specific CSS, or things like cannonical links, RSS links, etc:
/// <overloads>
/// Adds a CSS link to the page. Useful when you don't have access to the
/// HeadContent ContentPlaceHolder. This method has 4 overloads.
/// </overloads>
/// <summary>
/// Adds a CSS link.
/// </summary>
/// <param name="pathToCss">The path to CSS file.</param>
public void AddCss(string pathToCss) {
AddCss(pathToCss, string.Empty);
}
/// <summary>
/// Adds a CSS link in a specific position.
/// </summary>
/// <param name="pathToCss">The path to CSS.</param>
/// <param name="position">The postion.</param>
public void AddCss(string pathToCss, int? position) {
AddCss(pathToCss, string.Empty, position);
}
/// <summary>
/// Adds a CSS link to the page with a specific media type.
/// </summary>
/// <param name="pathToCss">The path to CSS file.</param>
/// <param name="media">The media type this stylesheet relates to.</param>
public void AddCss(string pathToCss, string media) {
AddHeaderLink(pathToCss, "text/css", "Stylesheet", media, null);
}
/// <summary>
/// Adds a CSS link to the page with a specific media type in a specific
/// position.
/// </summary>
/// <param name="pathToCss">The path to CSS.</param>
/// <param name="media">The media.</param>
/// <param name="position">The postion.</param>
public void AddCss(string pathToCss, string media, int? position) {
AddHeaderLink(pathToCss, "text/css", "Stylesheet", media, position);
}
/// <overloads>
/// Adds a general header link. Useful when you don't have access to the
/// HeadContent ContentPlaceHolder. This method has 3 overloads.
/// </overloads>
/// <summary>
/// Adds a general header link.
/// </summary>
/// <param name="href">The path to the resource.</param>
/// <param name="type">The type of the resource.</param>
public void AddHeaderLink(string href, string type) {
AddHeaderLink(href, type, string.Empty, string.Empty, null);
}
/// <summary>
/// Adds a general header link.
/// </summary>
/// <param name="href">The path to the resource.</param>
/// <param name="type">The type of the resource.</param>
/// <param name="rel">The relation of the resource to the page.</param>
public void AddHeaderLink(string href, string type, string rel) {
AddHeaderLink(href, type, rel, string.Empty, null);
}
/// <summary>
/// Adds a general header link.
/// </summary>
/// <param name="href">The path to the resource.</param>
/// <param name="type">The type of the resource.</param>
/// <param name="rel">The relation of the resource to the page.</param>
/// <param name="media">The media target of the link.</param>
public void AddHeaderLink(string href, string type, string rel, string media)
{
AddHeaderLink(href, type, rel, media, null);
}
/// <summary>
/// Adds a general header link.
/// </summary>
/// <param name="href">The path to the resource.</param>
/// <param name="type">The type of the resource.</param>
/// <param name="rel">The relation of the resource to the page.</param>
/// <param name="media">The media target of the link.</param>
/// <param name="position">The postion in the control order - leave as null
/// to append to the end.</param>
public void AddHeaderLink(string href, string type, string rel, string media,
int? position) {
var link = new HtmlLink { Href = href };
if (0 != type.Length) {
link.Attributes.Add(HtmlTextWriterAttribute.Type.ToString().ToLower(),
type);
}
if (0 != rel.Length) {
link.Attributes.Add(HtmlTextWriterAttribute.Rel.ToString().ToLower(),
rel);
}
if (0 != media.Length) {
link.Attributes.Add("media", media);
}
if (null == position || -1 == position) {
Page.Header.Controls.Add(link);
}
else
{
Page.Header.Controls.AddAt((int)position, link);
}
}
Culture initialization by overriding InitializeCulture() method (set culture and ui culture from cookie or DB).
Some of my applications are brandable, then here I do some "branding" stuff too.
I use this methot and thanks for yours,
/// <summary>
/// Displays the alert.
/// </summary>
/// <param name="message">The message to display.</param>
protected virtual void DisplayAlert(string message)
{
ClientScript.RegisterStartupScript(
GetType(),
Guid.NewGuid().ToString(),
string.Format("alert('{0}');", message.Replace("'", #"\'")),
true
);
}
/// <summary>
/// Finds the control recursive.
/// </summary>
/// <param name="id">The id.</param>
/// <returns>control</returns>
protected virtual Control FindControlRecursive(string id)
{
return FindControlRecursive(id, this);
}
/// <summary>
/// Finds the control recursive.
/// </summary>
/// <param name="id">The id.</param>
/// <param name="parent">The parent.</param>
/// <returns>control</returns>
protected virtual Control FindControlRecursive(string id, Control parent)
{
if (string.Compare(parent.ID, id, true) == 0)
return parent;
foreach (Control child in parent.Controls)
{
Control match = FindControlRecursive(id, child);
if (match != null)
return match;
}
return null;
}
Putting authorization code in a base page is generally not a good idea. The problem is, what happens if you forget to derive a page that needs authorization from the base page? You will have a security hole.
It's much better to use an HttpModule, so that you can intercept requests for all pages, and make sure users are authorized even before the HttpHandler has a chance to run.
Also, as others have said, and in keeping with OO principles, it's better to only have methods in your base page that actually relate to the Page itself. If they don't reference "this," they should probably be in a helper class -- or perhaps be extension methods.
I inherit from System.Web.UI.Page when I need certain properties and every page. This is good for aweb application that implements a login. In the membership pages I use my own base class to get access to Properties like UserID, UserName etc. These properties wrap Session Variables
Here are some examples (sans code) that I use a custom base class for:
Adding a page filter (e.g. replace "{theme}" with "~/App_Theme/[currentTheme]"),
Adding a Property and handling for Auto Titling pages based upon Site Map,
Registering specialized logging (could probably be redone via different means),
Adding methods for generalized input(Form/Querystring) validation, with blanket redirector: AddRequiredInput("WidgetID", PageInputType.QueryString, typeof(string)),
Site Map Helpers, allowing for things like changing a static "Edit Class" into something context related like "Edit Fall '10 Science 101"
ViewState Helpers, allowing me to register variable on the page to a name and have it automatically populate that variable from the viewstate or a default, and save the value back out to the viewstate at the end of the request.
Custom 404 Redirector, where I can pass an exception or message (or both) and it will go to a page I have predefined to nicely display and log it.
I personally like #5 the most because a) updating the SiteMap is ugly and I prefer not to have clutter the page, making it more readable, b) It makes the SiteMap much more user friendly.
,
Please refer Getting page specific info in ASP.Net Base Page
public abstract string AppSettingsRolesName { get; }
List<string> authorizedRoles = new List<string>((ConfigurationManager.AppSettings[AppSettingsRolesName]).Split(','))
if (!authorizedRoles.Contains(userRole))
{
Response.Redirect("UnauthorizedPage.aspx");
}
In derived Page
public override string AppSettingsRolesName
{
get { return "LogsScreenRoles"; }
}